context_menu_matcher.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/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/profiles/profile.h"
10#include "content/public/common/context_menu_params.h"
11#include "ui/gfx/favicon_size.h"
12
13namespace extensions {
14
15// static
16const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength = 75;
17
18ContextMenuMatcher::ContextMenuMatcher(
19    Profile* profile,
20    ui::SimpleMenuModel::Delegate* delegate,
21    ui::SimpleMenuModel* menu_model,
22    const base::Callback<bool(const MenuItem*)>& filter)
23    : profile_(profile), menu_model_(menu_model), delegate_(delegate),
24      filter_(filter) {
25}
26
27void ContextMenuMatcher::AppendExtensionItems(const std::string& extension_id,
28                                              const string16& selection_text,
29                                              int* index)
30{
31  ExtensionService* service = profile_->GetExtensionService();
32  MenuManager* manager = service->menu_manager();
33  const Extension* extension = service->GetExtensionById(extension_id, false);
34  DCHECK_GE(*index, 0);
35  int max_index =
36      IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST - IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST;
37  if (!extension || *index >= max_index)
38    return;
39
40  // Find matching items.
41  const MenuItem::List* all_items = manager->MenuItems(extension_id);
42  if (!all_items || all_items->empty())
43    return;
44  bool can_cross_incognito = service->CanCrossIncognito(extension);
45  MenuItem::List items = GetRelevantExtensionItems(*all_items,
46                                                   can_cross_incognito);
47
48  if (items.empty())
49    return;
50
51  // If this is the first extension-provided menu item, and there are other
52  // items in the menu, and the last item is not a separator add a separator.
53  if (*index == 0 && menu_model_->GetItemCount() &&
54      menu_model_->GetTypeAt(menu_model_->GetItemCount() - 1) !=
55          ui::MenuModel::TYPE_SEPARATOR)
56    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
57
58  // Extensions (other than platform apps) are only allowed one top-level slot
59  // (and it can't be a radio or checkbox item because we are going to put the
60  // extension icon next to it).
61  // If they have more than that, we automatically push them into a submenu.
62  if (extension->is_platform_app()) {
63    RecursivelyAppendExtensionItems(items, can_cross_incognito, selection_text,
64                                    menu_model_, index);
65  } else {
66    int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
67    string16 title;
68    MenuItem::List submenu_items;
69
70    if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) {
71      title = UTF8ToUTF16(extension->name());
72      submenu_items = items;
73    } else {
74      MenuItem* item = items[0];
75      extension_item_map_[menu_id] = item->id();
76      title = item->TitleWithReplacement(selection_text,
77                                       kMaxExtensionItemTitleLength);
78      submenu_items = GetRelevantExtensionItems(item->children(),
79                                                can_cross_incognito);
80    }
81
82    // Now add our item(s) to the menu_model_.
83    if (submenu_items.empty()) {
84      menu_model_->AddItem(menu_id, title);
85    } else {
86      ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
87      extension_menu_models_.push_back(submenu);
88      menu_model_->AddSubMenu(menu_id, title, submenu);
89      RecursivelyAppendExtensionItems(submenu_items, can_cross_incognito,
90                                      selection_text, submenu, index);
91    }
92    SetExtensionIcon(extension_id);
93  }
94}
95
96void ContextMenuMatcher::Clear() {
97  extension_item_map_.clear();
98  extension_menu_models_.clear();
99}
100
101bool ContextMenuMatcher::IsCommandIdChecked(int command_id) const {
102  MenuItem* item = GetExtensionMenuItem(command_id);
103  if (!item)
104    return false;
105  return item->checked();
106}
107
108bool ContextMenuMatcher::IsCommandIdEnabled(int command_id) const {
109  MenuItem* item = GetExtensionMenuItem(command_id);
110  if (!item)
111    return true;
112  return item->enabled();
113}
114
115void ContextMenuMatcher::ExecuteCommand(int command_id,
116    content::WebContents* web_contents,
117    const content::ContextMenuParams& params) {
118  MenuManager* manager = profile_->GetExtensionService()->menu_manager();
119  MenuItem* item = GetExtensionMenuItem(command_id);
120  if (!item)
121    return;
122
123  manager->ExecuteCommand(profile_, web_contents, params, item->id());
124}
125
126MenuItem::List ContextMenuMatcher::GetRelevantExtensionItems(
127    const MenuItem::List& items,
128    bool can_cross_incognito) {
129  MenuItem::List result;
130  for (MenuItem::List::const_iterator i = items.begin();
131       i != items.end(); ++i) {
132    const MenuItem* item = *i;
133
134    if (!filter_.Run(item))
135      continue;
136
137    if (item->id().incognito == profile_->IsOffTheRecord() ||
138        can_cross_incognito)
139      result.push_back(*i);
140  }
141  return result;
142}
143
144void ContextMenuMatcher::RecursivelyAppendExtensionItems(
145    const MenuItem::List& items,
146    bool can_cross_incognito,
147    const string16& selection_text,
148    ui::SimpleMenuModel* menu_model,
149    int* index)
150{
151  MenuItem::Type last_type = MenuItem::NORMAL;
152  int radio_group_id = 1;
153
154  for (MenuItem::List::const_iterator i = items.begin();
155       i != items.end(); ++i) {
156    MenuItem* item = *i;
157
158    // If last item was of type radio but the current one isn't, auto-insert
159    // a separator.  The converse case is handled below.
160    if (last_type == MenuItem::RADIO &&
161        item->type() != MenuItem::RADIO) {
162      menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
163      last_type = MenuItem::SEPARATOR;
164    }
165
166    int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
167    if (menu_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
168      return;
169    extension_item_map_[menu_id] = item->id();
170    string16 title = item->TitleWithReplacement(selection_text,
171                                                kMaxExtensionItemTitleLength);
172    if (item->type() == MenuItem::NORMAL) {
173      MenuItem::List children =
174          GetRelevantExtensionItems(item->children(), can_cross_incognito);
175      if (children.empty()) {
176        menu_model->AddItem(menu_id, title);
177      } else {
178        ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
179        extension_menu_models_.push_back(submenu);
180        menu_model->AddSubMenu(menu_id, title, submenu);
181        RecursivelyAppendExtensionItems(children, can_cross_incognito,
182                                        selection_text, submenu, index);
183      }
184    } else if (item->type() == MenuItem::CHECKBOX) {
185      menu_model->AddCheckItem(menu_id, title);
186    } else if (item->type() == MenuItem::RADIO) {
187      if (i != items.begin() &&
188          last_type != MenuItem::RADIO) {
189        radio_group_id++;
190
191        // Auto-append a separator if needed.
192        if (last_type != MenuItem::SEPARATOR)
193          menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
194      }
195
196      menu_model->AddRadioItem(menu_id, title, radio_group_id);
197    } else if (item->type() == MenuItem::SEPARATOR) {
198      if (i != items.begin() && last_type != MenuItem::SEPARATOR) {
199        menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
200      }
201    }
202    last_type = item->type();
203  }
204}
205
206MenuItem* ContextMenuMatcher::GetExtensionMenuItem(int id) const {
207  MenuManager* manager = profile_->GetExtensionService()->menu_manager();
208  std::map<int, MenuItem::Id>::const_iterator i =
209      extension_item_map_.find(id);
210  if (i != extension_item_map_.end()) {
211    MenuItem* item = manager->GetItemById(i->second);
212    if (item)
213      return item;
214  }
215  return NULL;
216}
217
218void ContextMenuMatcher::SetExtensionIcon(const std::string& extension_id) {
219  ExtensionService* service = profile_->GetExtensionService();
220  MenuManager* menu_manager = service->menu_manager();
221
222  int index = menu_model_->GetItemCount() - 1;
223  DCHECK_GE(index, 0);
224
225  const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id);
226  DCHECK(icon.width() == gfx::kFaviconSize);
227  DCHECK(icon.height() == gfx::kFaviconSize);
228
229  menu_model_->SetIcon(index, gfx::Image(icon));
230}
231
232}  // namespace extensions
233