blob: 0fef92af8119a5ebd7e414e4f76dc545d827c016 [file] [log] [blame]
[email protected]4f8a4d12012-09-28 19:23:091// 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]f5fede02014-07-29 02:48:215#include "chrome/browser/extensions/context_menu_matcher.h"
6
[email protected]112158af2013-06-07 23:46:187#include "base/strings/utf_string_conversions.h"
[email protected]4f8a4d12012-09-28 19:23:098#include "chrome/app/chrome_command_ids.h"
[email protected]4f8a4d12012-09-28 19:23:099#include "chrome/browser/extensions/extension_service.h"
[email protected]a7ff4b72013-10-17 20:56:0210#include "chrome/browser/extensions/extension_util.h"
[email protected]f5fede02014-07-29 02:48:2111#include "content/public/browser/browser_context.h"
[email protected]4f8a4d12012-09-28 19:23:0912#include "content/public/common/context_menu_params.h"
[email protected]59b0e602014-01-30 00:41:2413#include "extensions/browser/extension_system.h"
[email protected]4f8a4d12012-09-28 19:23:0914#include "ui/gfx/favicon_size.h"
[email protected]f34efa22013-03-05 19:14:2315#include "ui/gfx/image/image.h"
[email protected]4f8a4d12012-09-28 19:23:0916
17namespace extensions {
18
19// static
20const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength = 75;
21
22ContextMenuMatcher::ContextMenuMatcher(
[email protected]f5fede02014-07-29 02:48:2123 content::BrowserContext* browser_context,
[email protected]4f8a4d12012-09-28 19:23:0924 ui::SimpleMenuModel::Delegate* delegate,
25 ui::SimpleMenuModel* menu_model,
26 const base::Callback<bool(const MenuItem*)>& filter)
[email protected]f5fede02014-07-29 02:48:2127 : browser_context_(browser_context),
28 menu_model_(menu_model),
29 delegate_(delegate),
[email protected]4f8a4d12012-09-28 19:23:0930 filter_(filter) {
31}
32
[email protected]439f1e32013-12-09 20:09:0933void ContextMenuMatcher::AppendExtensionItems(
[email protected]6f9d2c62014-03-10 12:12:0534 const MenuItem::ExtensionKey& extension_key,
[email protected]439f1e32013-12-09 20:09:0935 const base::string16& selection_text,
36 int* index) {
[email protected]4f8a4d12012-09-28 19:23:0937 DCHECK_GE(*index, 0);
38 int max_index =
39 IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST - IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST;
[email protected]0ea8fac2013-06-12 15:31:3540 if (*index >= max_index)
[email protected]4f8a4d12012-09-28 19:23:0941 return;
42
[email protected]0ea8fac2013-06-12 15:31:3543 const Extension* extension = NULL;
44 MenuItem::List items;
45 bool can_cross_incognito;
[email protected]6f9d2c62014-03-10 12:12:0546 if (!GetRelevantExtensionTopLevelItems(
47 extension_key, &extension, &can_cross_incognito, items))
[email protected]4f8a4d12012-09-28 19:23:0948 return;
[email protected]4f8a4d12012-09-28 19:23:0949
50 if (items.empty())
51 return;
52
53 // If this is the first extension-provided menu item, and there are other
54 // items in the menu, and the last item is not a separator add a separator.
[email protected]00491c052013-02-08 10:53:2555 if (*index == 0 && menu_model_->GetItemCount())
[email protected]4f8a4d12012-09-28 19:23:0956 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)++;
[email protected]439f1e32013-12-09 20:09:0967 base::string16 title;
[email protected]4f8a4d12012-09-28 19:23:0968 MenuItem::List submenu_items;
69
70 if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) {
[email protected]04338722013-12-24 23:18:0571 title = base::UTF8ToUTF16(extension->name());
[email protected]4f8a4d12012-09-28 19:23:0972 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 }
[email protected]6f9d2c62014-03-10 12:12:0592 SetExtensionIcon(extension_key.extension_id);
[email protected]4f8a4d12012-09-28 19:23:0993 }
94}
95
96void ContextMenuMatcher::Clear() {
97 extension_item_map_.clear();
98 extension_menu_models_.clear();
99}
100
[email protected]0ea8fac2013-06-12 15:31:35101base::string16 ContextMenuMatcher::GetTopLevelContextMenuTitle(
[email protected]6f9d2c62014-03-10 12:12:05102 const MenuItem::ExtensionKey& extension_key,
[email protected]439f1e32013-12-09 20:09:09103 const base::string16& selection_text) {
[email protected]0ea8fac2013-06-12 15:31:35104 const Extension* extension = NULL;
105 MenuItem::List items;
106 bool can_cross_incognito;
[email protected]6f9d2c62014-03-10 12:12:05107 GetRelevantExtensionTopLevelItems(
108 extension_key, &extension, &can_cross_incognito, items);
[email protected]0ea8fac2013-06-12 15:31:35109
110 base::string16 title;
111
112 if (items.empty() ||
113 items.size() > 1 ||
114 items[0]->type() != MenuItem::NORMAL) {
[email protected]04338722013-12-24 23:18:05115 title = base::UTF8ToUTF16(extension->name());
[email protected]0ea8fac2013-06-12 15:31:35116 } else {
117 MenuItem* item = items[0];
118 title = item->TitleWithReplacement(
119 selection_text, kMaxExtensionItemTitleLength);
120 }
121 return title;
122}
123
[email protected]4f8a4d12012-09-28 19:23:09124bool ContextMenuMatcher::IsCommandIdChecked(int command_id) const {
125 MenuItem* item = GetExtensionMenuItem(command_id);
126 if (!item)
127 return false;
128 return item->checked();
129}
130
131bool ContextMenuMatcher::IsCommandIdEnabled(int command_id) const {
132 MenuItem* item = GetExtensionMenuItem(command_id);
133 if (!item)
134 return true;
135 return item->enabled();
136}
137
138void ContextMenuMatcher::ExecuteCommand(int command_id,
139 content::WebContents* web_contents,
140 const content::ContextMenuParams& params) {
[email protected]4f8a4d12012-09-28 19:23:09141 MenuItem* item = GetExtensionMenuItem(command_id);
142 if (!item)
143 return;
144
[email protected]f5fede02014-07-29 02:48:21145 MenuManager* manager = MenuManager::Get(browser_context_);
146 manager->ExecuteCommand(browser_context_, web_contents, params, item->id());
[email protected]4f8a4d12012-09-28 19:23:09147}
148
[email protected]0ea8fac2013-06-12 15:31:35149bool ContextMenuMatcher::GetRelevantExtensionTopLevelItems(
[email protected]6f9d2c62014-03-10 12:12:05150 const MenuItem::ExtensionKey& extension_key,
[email protected]0ea8fac2013-06-12 15:31:35151 const Extension** extension,
152 bool* can_cross_incognito,
153 MenuItem::List& items) {
154 ExtensionService* service =
[email protected]f5fede02014-07-29 02:48:21155 extensions::ExtensionSystem::Get(browser_context_)->extension_service();
[email protected]6f9d2c62014-03-10 12:12:05156 *extension = service->GetExtensionById(extension_key.extension_id, false);
[email protected]0ea8fac2013-06-12 15:31:35157
158 if (!*extension)
159 return false;
160
161 // Find matching items.
[email protected]f5fede02014-07-29 02:48:21162 MenuManager* manager = MenuManager::Get(browser_context_);
[email protected]6f9d2c62014-03-10 12:12:05163 const MenuItem::List* all_items = manager->MenuItems(extension_key);
[email protected]0ea8fac2013-06-12 15:31:35164 if (!all_items || all_items->empty())
165 return false;
166
[email protected]f5fede02014-07-29 02:48:21167 *can_cross_incognito = util::CanCrossIncognito(*extension, browser_context_);
[email protected]0ea8fac2013-06-12 15:31:35168 items = GetRelevantExtensionItems(*all_items,
169 *can_cross_incognito);
170
171 return true;
172}
173
[email protected]4f8a4d12012-09-28 19:23:09174MenuItem::List ContextMenuMatcher::GetRelevantExtensionItems(
175 const MenuItem::List& items,
176 bool can_cross_incognito) {
177 MenuItem::List result;
178 for (MenuItem::List::const_iterator i = items.begin();
179 i != items.end(); ++i) {
180 const MenuItem* item = *i;
181
182 if (!filter_.Run(item))
183 continue;
184
[email protected]f5fede02014-07-29 02:48:21185 if (item->id().incognito == browser_context_->IsOffTheRecord() ||
[email protected]4f8a4d12012-09-28 19:23:09186 can_cross_incognito)
187 result.push_back(*i);
188 }
189 return result;
190}
191
192void ContextMenuMatcher::RecursivelyAppendExtensionItems(
193 const MenuItem::List& items,
194 bool can_cross_incognito,
[email protected]439f1e32013-12-09 20:09:09195 const base::string16& selection_text,
[email protected]4f8a4d12012-09-28 19:23:09196 ui::SimpleMenuModel* menu_model,
197 int* index)
198{
199 MenuItem::Type last_type = MenuItem::NORMAL;
200 int radio_group_id = 1;
201
202 for (MenuItem::List::const_iterator i = items.begin();
203 i != items.end(); ++i) {
204 MenuItem* item = *i;
205
206 // If last item was of type radio but the current one isn't, auto-insert
207 // a separator. The converse case is handled below.
208 if (last_type == MenuItem::RADIO &&
209 item->type() != MenuItem::RADIO) {
210 menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
211 last_type = MenuItem::SEPARATOR;
212 }
213
214 int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
215 if (menu_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
216 return;
217 extension_item_map_[menu_id] = item->id();
[email protected]439f1e32013-12-09 20:09:09218 base::string16 title = item->TitleWithReplacement(selection_text,
[email protected]4f8a4d12012-09-28 19:23:09219 kMaxExtensionItemTitleLength);
220 if (item->type() == MenuItem::NORMAL) {
221 MenuItem::List children =
222 GetRelevantExtensionItems(item->children(), can_cross_incognito);
223 if (children.empty()) {
224 menu_model->AddItem(menu_id, title);
225 } else {
226 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
227 extension_menu_models_.push_back(submenu);
228 menu_model->AddSubMenu(menu_id, title, submenu);
229 RecursivelyAppendExtensionItems(children, can_cross_incognito,
230 selection_text, submenu, index);
231 }
232 } else if (item->type() == MenuItem::CHECKBOX) {
233 menu_model->AddCheckItem(menu_id, title);
234 } else if (item->type() == MenuItem::RADIO) {
235 if (i != items.begin() &&
236 last_type != MenuItem::RADIO) {
237 radio_group_id++;
238
239 // Auto-append a separator if needed.
[email protected]00491c052013-02-08 10:53:25240 menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
[email protected]4f8a4d12012-09-28 19:23:09241 }
242
243 menu_model->AddRadioItem(menu_id, title, radio_group_id);
244 } else if (item->type() == MenuItem::SEPARATOR) {
[email protected]00491c052013-02-08 10:53:25245 menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
[email protected]4f8a4d12012-09-28 19:23:09246 }
247 last_type = item->type();
248 }
249}
250
251MenuItem* ContextMenuMatcher::GetExtensionMenuItem(int id) const {
[email protected]f5fede02014-07-29 02:48:21252 MenuManager* manager = MenuManager::Get(browser_context_);
[email protected]4f8a4d12012-09-28 19:23:09253 std::map<int, MenuItem::Id>::const_iterator i =
254 extension_item_map_.find(id);
255 if (i != extension_item_map_.end()) {
256 MenuItem* item = manager->GetItemById(i->second);
257 if (item)
258 return item;
259 }
260 return NULL;
261}
262
263void ContextMenuMatcher::SetExtensionIcon(const std::string& extension_id) {
[email protected]f5fede02014-07-29 02:48:21264 MenuManager* menu_manager = MenuManager::Get(browser_context_);
[email protected]4f8a4d12012-09-28 19:23:09265
266 int index = menu_model_->GetItemCount() - 1;
267 DCHECK_GE(index, 0);
268
269 const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id);
270 DCHECK(icon.width() == gfx::kFaviconSize);
271 DCHECK(icon.height() == gfx::kFaviconSize);
272
[email protected]32e7a9b2013-01-23 23:00:19273 menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon));
[email protected]4f8a4d12012-09-28 19:23:09274}
275
276} // namespace extensions