Skip to content

Commit

Permalink
Give platform apps control over launcher right-click context menu.
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 16 changed files with 491 additions and 275 deletions.
18 changes: 18 additions & 0 deletions chrome/browser/extensions/api/context_menu/context_menu_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const char kParentsMustBeNormalError[] =
"Parent items must have type \"normal\"";
const char kTitleNeededError[] =
"All menu items except for separators must have a title";
const char kLauncherNotAllowedError[] =
"Only packaged apps are allowed to use 'launcher' context";

std::string GetIDString(const extensions::MenuItem::Id& id) {
if (id.uid == 0)
Expand Down Expand Up @@ -75,6 +77,9 @@ extensions::MenuItem::ContextList GetContexts(
case PropertyWithEnumT::CONTEXTS_ELEMENT_FRAME:
contexts.Add(extensions::MenuItem::FRAME);
break;
case PropertyWithEnumT::CONTEXTS_ELEMENT_LAUNCHER:
contexts.Add(extensions::MenuItem::LAUNCHER);
break;
case PropertyWithEnumT::CONTEXTS_ELEMENT_NONE:
NOTREACHED();
}
Expand Down Expand Up @@ -187,6 +192,12 @@ bool CreateContextMenuFunction::RunImpl() {
else
contexts.Add(MenuItem::PAGE);

if (contexts.Contains(MenuItem::LAUNCHER) &&
!GetExtension()->is_platform_app()) {
error_ = kLauncherNotAllowedError;
return false;
}

MenuItem::Type type = GetType(params->create_properties);

if (title.empty() && type != MenuItem::SEPARATOR) {
Expand Down Expand Up @@ -303,6 +314,13 @@ bool UpdateContextMenuFunction::RunImpl() {
MenuItem::ContextList contexts;
if (params->update_properties.contexts.get()) {
contexts = GetContexts(params->update_properties);

if (contexts.Contains(MenuItem::LAUNCHER) &&
!GetExtension()->is_platform_app()) {
error_ = kLauncherNotAllowedError;
return false;
}

if (contexts != item->contexts())
item->set_contexts(contexts);
}
Expand Down
232 changes: 232 additions & 0 deletions chrome/browser/extensions/context_menu_matcher.cc
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
86 changes: 86 additions & 0 deletions chrome/browser/extensions/context_menu_matcher.h
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_
Loading

0 comments on commit 4f8a4d1

Please sign in to comment.