1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
|
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/context_menu_helpers.h"
#include <stddef.h>
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/context_menu_matcher.h"
#include "components/renderer_context_menu/render_view_context_menu_base.h"
#include "content/public/browser/context_menu_params.h"
#include "third_party/blink/public/mojom/context_menu/context_menu.mojom-shared.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/text_elider.h"
using blink::mojom::ContextMenuDataMediaType;
namespace extensions {
namespace context_menu_helpers {
namespace {
// Helper function to determine if a given URL matches a URLPatternSet.
// This is primarily a wrapper around URLPatternSet::MatchesURL but includes an
// important special case: An empty pattern set is considered a match.
// This convention is used throughout the extension system to mean that an item
// has no URL-specific restrictions.
//
// @param patterns The set of URL patterns to check against.
// @param url The URL to be tested.
// @return true if the URL is matched by the patterns or if the pattern set
// is empty, false otherwise.
bool ExtensionPatternMatch(const URLPatternSet& patterns, const GURL& url) {
// No patterns means no restriction, so that implicitly matches.
if (patterns.is_empty()) {
return true;
}
return patterns.MatchesURL(url);
}
// Escapes ampersands in a string by replacing each `&` with `&&`.
// This is necessary for strings that will be displayed in UI elements like
// menus or labels, as a single ampersand is often interpreted as a prefix for
// a mnemonic character (e.g., "S&ave" would display as "Save" with 'a'
// underlined). Doubling the ampersand displays a literal '&'. The string is
// modified in-place.
//
// @param text The string to modify.
void EscapeAmpersands(std::u16string* text) {
base::ReplaceChars(*text, u"&", u"&&", text);
}
} // namespace
const char kActionNotAllowedError[] =
"Only extensions are allowed to use action contexts";
const char kCannotFindItemError[] = "Cannot find menu item with id *";
const char kCheckedError[] =
"Only items with type \"radio\" or \"checkbox\" can be checked";
const char kDuplicateIDError[] =
"Cannot create item with duplicate id *";
const char kGeneratedIdKey[] = "generatedId";
const char kLauncherNotAllowedError[] =
"Only packaged apps are allowed to use 'launcher' context";
const char kOnclickDisallowedError[] =
"Extensions using event pages or "
"Service Workers cannot pass an onclick parameter to "
"chrome.contextMenus.create. Instead, use the "
"chrome.contextMenus.onClicked event.";
const char kParentsMustBeNormalError[] =
"Parent items must have type \"normal\"";
const char kTitleNeededError[] =
"All menu items except for separators must have a title";
const char kTooManyMenuItems[] =
"An extension can create a maximum of * menu items.";
std::string GetIDString(const MenuItem::Id& id) {
if (id.uid == 0) {
return id.string_uid;
} else {
return base::NumberToString(id.uid);
}
}
MenuItem* GetParent(MenuItem::Id parent_id,
const MenuManager* menu_manager,
std::string* error) {
MenuItem* parent = menu_manager->GetItemById(parent_id);
if (!parent) {
*error = ErrorUtils::FormatErrorMessage(
kCannotFindItemError, GetIDString(parent_id));
return nullptr;
}
if (parent->type() != MenuItem::NORMAL) {
*error = kParentsMustBeNormalError;
return nullptr;
}
return parent;
}
MenuItem::ContextList GetContexts(
const std::vector<api::context_menus::ContextType>& in_contexts) {
MenuItem::ContextList contexts;
for (auto context : in_contexts) {
switch (context) {
case api::context_menus::ContextType::kAll:
contexts.Add(MenuItem::ALL);
break;
case api::context_menus::ContextType::kPage:
contexts.Add(MenuItem::PAGE);
break;
case api::context_menus::ContextType::kSelection:
contexts.Add(MenuItem::SELECTION);
break;
case api::context_menus::ContextType::kLink:
contexts.Add(MenuItem::LINK);
break;
case api::context_menus::ContextType::kEditable:
contexts.Add(MenuItem::EDITABLE);
break;
case api::context_menus::ContextType::kImage:
contexts.Add(MenuItem::IMAGE);
break;
case api::context_menus::ContextType::kVideo:
contexts.Add(MenuItem::VIDEO);
break;
case api::context_menus::ContextType::kAudio:
contexts.Add(MenuItem::AUDIO);
break;
case api::context_menus::ContextType::kFrame:
contexts.Add(MenuItem::FRAME);
break;
case api::context_menus::ContextType::kLauncher:
// Not available for <webview>.
contexts.Add(MenuItem::LAUNCHER);
break;
case api::context_menus::ContextType::kBrowserAction:
// Not available for <webview>.
contexts.Add(MenuItem::BROWSER_ACTION);
break;
case api::context_menus::ContextType::kPageAction:
// Not available for <webview>.
contexts.Add(MenuItem::PAGE_ACTION);
break;
case api::context_menus::ContextType::kAction:
// Not available for <webview>.
contexts.Add(MenuItem::ACTION);
break;
case api::context_menus::ContextType::kNone:
NOTREACHED();
}
}
return contexts;
}
MenuItem::Type GetType(api::context_menus::ItemType type,
MenuItem::Type default_type) {
switch (type) {
case api::context_menus::ItemType::kNone:
return default_type;
case api::context_menus::ItemType::kNormal:
return MenuItem::NORMAL;
case api::context_menus::ItemType::kCheckbox:
return MenuItem::CHECKBOX;
case api::context_menus::ItemType::kRadio:
return MenuItem::RADIO;
case api::context_menus::ItemType::kSeparator:
return MenuItem::SEPARATOR;
}
return MenuItem::NORMAL;
}
bool ExtensionContextAndPatternMatch(const content::ContextMenuParams& params,
const MenuItem::ContextList& contexts,
const URLPatternSet& target_url_patterns) {
const bool has_link = !params.link_url.is_empty();
const bool has_selection = !params.selection_text.empty();
const bool in_subframe = params.is_subframe;
if (contexts.Contains(MenuItem::ALL) ||
(has_selection && contexts.Contains(MenuItem::SELECTION)) ||
(params.is_editable && contexts.Contains(MenuItem::EDITABLE)) ||
(in_subframe && contexts.Contains(MenuItem::FRAME))) {
return true;
}
if (has_link && contexts.Contains(MenuItem::LINK) &&
ExtensionPatternMatch(target_url_patterns, params.link_url)) {
return true;
}
switch (params.media_type) {
case ContextMenuDataMediaType::kImage:
if (contexts.Contains(MenuItem::IMAGE) &&
ExtensionPatternMatch(target_url_patterns, params.src_url)) {
return true;
}
break;
case ContextMenuDataMediaType::kVideo:
if (contexts.Contains(MenuItem::VIDEO) &&
ExtensionPatternMatch(target_url_patterns, params.src_url)) {
return true;
}
break;
case ContextMenuDataMediaType::kAudio:
if (contexts.Contains(MenuItem::AUDIO) &&
ExtensionPatternMatch(target_url_patterns, params.src_url)) {
return true;
}
break;
default:
break;
}
// PAGE is the least specific context, so we only examine that if none of the
// other contexts apply (except for FRAME, which is included in PAGE for
// backwards compatibility).
if (!has_link && !has_selection && !params.is_editable &&
params.media_type == ContextMenuDataMediaType::kNone &&
contexts.Contains(MenuItem::PAGE)) {
return true;
}
return false;
}
bool MenuItemMatchesParams(const content::ContextMenuParams& params,
const MenuItem* item) {
bool match = ExtensionContextAndPatternMatch(params, item->contexts(),
item->target_url_patterns());
if (!match) {
return false;
}
return ExtensionPatternMatch(item->document_url_patterns(), params.frame_url);
}
std::u16string PrintableSelectionText(const std::u16string& selection_text) {
std::u16string result = gfx::TruncateString(
selection_text, RenderViewContextMenuBase::kMaxSelectionTextLength,
gfx::WORD_BREAK);
EscapeAmpersands(&result);
return result;
}
void PopulateExtensionItems(content::BrowserContext* browser_context,
const content::ContextMenuParams& params,
ContextMenuMatcher& matcher) {
matcher.Clear();
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context);
MenuManager* menu_manager = MenuManager::Get(browser_context);
if (!menu_manager || !registry) {
return;
}
std::u16string printable_selection_text =
context_menu_helpers::PrintableSelectionText(params.selection_text);
// Get a list of extension id's that have context menu items, and sort by the
// top level context menu title of the extension.
std::vector<std::u16string> sorted_menu_titles;
std::map<std::u16string, std::vector<const Extension*>>
title_to_extensions_map;
for (const auto& id : menu_manager->ExtensionIds()) {
const Extension* extension =
registry->enabled_extensions().GetByID(id.extension_id);
// Platform apps have their context menus created directly in
// AppendPlatformAppItems.
if (extension && !extension->is_platform_app()) {
std::u16string menu_title =
matcher.GetTopLevelContextMenuTitle(id, printable_selection_text);
title_to_extensions_map[menu_title].push_back(extension);
sorted_menu_titles.push_back(menu_title);
}
}
if (sorted_menu_titles.empty()) {
return;
}
const std::string app_locale = g_browser_process->GetApplicationLocale();
l10n_util::SortStrings16(app_locale, &sorted_menu_titles);
sorted_menu_titles.erase(
std::unique(sorted_menu_titles.begin(), sorted_menu_titles.end()),
sorted_menu_titles.end());
sorted_menu_titles.erase(
std::unique(sorted_menu_titles.begin(), sorted_menu_titles.end()),
sorted_menu_titles.end());
int index = 0;
for (const auto& title : sorted_menu_titles) {
const std::vector<const Extension*>& extensions =
title_to_extensions_map[title];
for (const Extension* extension : extensions) {
MenuItem::ExtensionKey extension_key(extension->id());
matcher.AppendExtensionItems(extension_key, printable_selection_text,
&index,
/*is_action_menu=*/false);
}
}
}
} // namespace context_menu_helpers
} // namespace extensions
|