forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Give platform apps control over launcher right-click context menu.
This moves most of the extension context menu related code from RenderViewContextMenu to a separate class, which is then used in two other places as well. This time without a bug that caused the 'tab' parameter in the onClicked event to no longer get valid values (as well as an extension to a test so this problem is not just caught in debug builds but also in tests run in release builds). See http://codereview.chromium.org/10918103/ for the previous go at this patch. BUG=143222 Review URL: https://chromiumcodereview.appspot.com/10979036 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@159312 0039d316-1c4b-4281-b951-d872f2087c98
- Loading branch information
mek@chromium.org
committed
Sep 28, 2012
1 parent
280453e
commit 4f8a4d1
Showing
16 changed files
with
491 additions
and
275 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
// 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/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/profiles/profile.h" | ||
#include "content/public/common/context_menu_params.h" | ||
#include "ui/gfx/favicon_size.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 string16& selection_text, | ||
int* index) | ||
{ | ||
ExtensionService* service = profile_->GetExtensionService(); | ||
MenuManager* manager = service->menu_manager(); | ||
const Extension* extension = service->GetExtensionById(extension_id, false); | ||
DCHECK_GE(*index, 0); | ||
int max_index = | ||
IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST - IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST; | ||
if (!extension || *index >= max_index) | ||
return; | ||
|
||
// Find matching items. | ||
const MenuItem::List* all_items = manager->MenuItems(extension_id); | ||
if (!all_items || all_items->empty()) | ||
return; | ||
bool can_cross_incognito = service->CanCrossIncognito(extension); | ||
MenuItem::List items = GetRelevantExtensionItems(*all_items, | ||
can_cross_incognito); | ||
|
||
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_->GetTypeAt(menu_model_->GetItemCount() - 1) != | ||
ui::MenuModel::TYPE_SEPARATOR) | ||
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)++; | ||
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(); | ||
} | ||
|
||
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) { | ||
MenuManager* manager = profile_->GetExtensionService()->menu_manager(); | ||
MenuItem* item = GetExtensionMenuItem(command_id); | ||
if (!item) | ||
return; | ||
|
||
manager->ExecuteCommand(profile_, web_contents, params, item->id()); | ||
} | ||
|
||
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 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(); | ||
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. | ||
if (last_type != MenuItem::SEPARATOR) | ||
menu_model->AddSeparator(ui::NORMAL_SEPARATOR); | ||
} | ||
|
||
menu_model->AddRadioItem(menu_id, title, radio_group_id); | ||
} else if (item->type() == MenuItem::SEPARATOR) { | ||
if (i != items.begin() && last_type != MenuItem::SEPARATOR) { | ||
menu_model->AddSeparator(ui::NORMAL_SEPARATOR); | ||
} | ||
} | ||
last_type = item->type(); | ||
} | ||
} | ||
|
||
MenuItem* ContextMenuMatcher::GetExtensionMenuItem(int id) const { | ||
MenuManager* manager = profile_->GetExtensionService()->menu_manager(); | ||
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) { | ||
ExtensionService* service = profile_->GetExtensionService(); | ||
MenuManager* menu_manager = service->menu_manager(); | ||
|
||
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(icon)); | ||
} | ||
|
||
} // namespace extensions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// 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. | ||
|
||
#ifndef CHROME_BROWSER_EXTENSIONS_CONTEXT_MENU_MATCHER_H_ | ||
#define CHROME_BROWSER_EXTENSIONS_CONTEXT_MENU_MATCHER_H_ | ||
|
||
#include <map> | ||
|
||
#include "base/callback.h" | ||
#include "base/memory/scoped_ptr.h" | ||
#include "base/memory/scoped_vector.h" | ||
#include "chrome/browser/extensions/menu_manager.h" | ||
#include "ui/base/models/simple_menu_model.h" | ||
|
||
class ExtensionContextMenuBrowserTest; | ||
class Profile; | ||
|
||
namespace extensions { | ||
|
||
// This class contains code that is shared between the various places where | ||
// context menu items added by the extension or app should be shown. | ||
class ContextMenuMatcher { | ||
public: | ||
static const size_t kMaxExtensionItemTitleLength; | ||
|
||
// The |filter| will be called on possibly matching menu items, and its | ||
// result is used to determine which items to actually append to the menu. | ||
ContextMenuMatcher(Profile* profile, | ||
ui::SimpleMenuModel::Delegate* delegate, | ||
ui::SimpleMenuModel* menu_model, | ||
const base::Callback<bool(const MenuItem*)>& filter); | ||
|
||
// This is a helper function to append items for one particular extension. | ||
// The |index| parameter is used for assigning id's, and is incremented for | ||
// each item actually added. | ||
void AppendExtensionItems(const std::string& extension_id, | ||
const string16& selection_text, | ||
int* index); | ||
|
||
void Clear(); | ||
|
||
bool IsCommandIdChecked(int command_id) const; | ||
bool IsCommandIdEnabled(int command_id) const; | ||
void ExecuteCommand(int command_id, | ||
content::WebContents* web_contents, | ||
const content::ContextMenuParams& params); | ||
|
||
private: | ||
friend class ::ExtensionContextMenuBrowserTest; | ||
|
||
MenuItem::List GetRelevantExtensionItems( | ||
const MenuItem::List& items, | ||
bool can_cross_incognito); | ||
|
||
// Used for recursively adding submenus of extension items. | ||
void RecursivelyAppendExtensionItems(const MenuItem::List& items, | ||
bool can_cross_incognito, | ||
const string16& selection_text, | ||
ui::SimpleMenuModel* menu_model, | ||
int* index); | ||
|
||
// Attempts to get an MenuItem given the id of a context menu item. | ||
extensions::MenuItem* GetExtensionMenuItem(int id) const; | ||
|
||
// This will set the icon on the most recently-added item in the menu_model_. | ||
void SetExtensionIcon(const std::string& extension_id); | ||
|
||
Profile* profile_; | ||
ui::SimpleMenuModel* menu_model_; | ||
ui::SimpleMenuModel::Delegate* delegate_; | ||
|
||
base::Callback<bool(const MenuItem*)> filter_; | ||
|
||
// Maps the id from a context menu item to the MenuItem's internal id. | ||
std::map<int, extensions::MenuItem::Id> extension_item_map_; | ||
|
||
// Keep track of and clean up menu models for submenus. | ||
ScopedVector<ui::SimpleMenuModel> extension_menu_models_; | ||
|
||
DISALLOW_COPY_AND_ASSIGN(ContextMenuMatcher); | ||
}; | ||
|
||
} // namespace extensions | ||
|
||
#endif // CHROME_BROWSER_EXTENSIONS_CONTEXT_MENU_MATCHER_H_ |
Oops, something went wrong.