// Copyright (c) 2011 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 <algorithm>
#include <set>
#include "chrome/browser/tab_contents/render_view_context_menu.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/autocomplete/autocomplete_classifier.h"
#include "chrome/browser/autocomplete/autocomplete_edit.h"
#include "chrome/browser/autocomplete/autocomplete_match.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/debugger/devtools_manager.h"
#include "chrome/browser/debugger/devtools_window.h"
#include "chrome/browser/download/download_manager.h"
#include "chrome/browser/download/download_util.h"
#include "chrome/browser/download/save_package.h"
#include "chrome/browser/extensions/extension_event_router.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/net/browser_url_util.h"
#include "chrome/browser/page_info_window.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/prefs/pref_member.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/printing/print_preview_tab_controller.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_model.h"
#include "chrome/browser/spellcheck_host.h"
#include "chrome/browser/spellchecker_platform_engine.h"
#include "chrome/browser/translate/translate_manager.h"
#include "chrome/browser/translate/translate_prefs.h"
#include "chrome/browser/translate/translate_tab_helper.h"
#include "chrome/browser/ui/download/download_tab_helper.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/content_restriction.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/print_messages.h"
#include "chrome/common/url_constants.h"
#include "content/browser/child_process_security_policy.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/renderer_host/render_widget_host_view.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "grit/generated_resources.h"
#include "net/base/escape.h"
#include "net/url_request/url_request.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebContextMenuData.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayerAction.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebTextDirection.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/favicon_size.h"
#include "webkit/glue/webmenuitem.h"
using WebKit::WebContextMenuData;
using WebKit::WebMediaPlayerAction;
namespace {
bool IsCustomItemEnabled(const std::vector<WebMenuItem>& items, int id) {
DCHECK(id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST);
for (size_t i = 0; i < items.size(); ++i) {
int action_id = IDC_CONTENT_CONTEXT_CUSTOM_FIRST + items[i].action;
if (action_id == id)
return items[i].enabled;
if (items[i].type == WebMenuItem::SUBMENU) {
if (IsCustomItemEnabled(items[i].submenu, id))
return true;
}
}
return false;
}
bool IsCustomItemChecked(const std::vector<WebMenuItem>& items, int id) {
DCHECK(id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST);
for (size_t i = 0; i < items.size(); ++i) {
int action_id = IDC_CONTENT_CONTEXT_CUSTOM_FIRST + items[i].action;
if (action_id == id)
return items[i].checked;
if (items[i].type == WebMenuItem::SUBMENU) {
if (IsCustomItemChecked(items[i].submenu, id))
return true;
}
}
return false;
}
const size_t kMaxCustomMenuDepth = 5;
const size_t kMaxCustomMenuTotalItems = 1000;
void AddCustomItemsToMenu(const std::vector<WebMenuItem>& items,
size_t depth,
size_t* total_items,
ui::SimpleMenuModel::Delegate* delegate,
ui::SimpleMenuModel* menu_model) {
if (depth > kMaxCustomMenuDepth) {
LOG(ERROR) << "Custom menu too deeply nested.";
return;
}
for (size_t i = 0; i < items.size(); ++i) {
if (IDC_CONTENT_CONTEXT_CUSTOM_FIRST + items[i].action >=
IDC_CONTENT_CONTEXT_CUSTOM_LAST) {
LOG(ERROR) << "Custom menu action value too big.";
return;
}
if (*total_items >= kMaxCustomMenuTotalItems) {
LOG(ERROR) << "Custom menu too large (too many items).";
return;
}
(*total_items)++;
switch (items[i].type) {
case WebMenuItem::OPTION:
menu_model->AddItem(
items[i].action + IDC_CONTENT_CONTEXT_CUSTOM_FIRST,
items[i].label);
break;
case WebMenuItem::CHECKABLE_OPTION:
menu_model->AddCheckItem(
items[i].action + IDC_CONTENT_CONTEXT_CUSTOM_FIRST,
items[i].label);
break;
case WebMenuItem::GROUP:
// TODO(viettrungluu): I don't know what this is supposed to do.
NOTREACHED();
break;
case WebMenuItem::SEPARATOR:
menu_model->AddSeparator();
break;
case WebMenuItem::SUBMENU: {
ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate);
AddCustomItemsToMenu(items[i].submenu, depth + 1, total_items, delegate,
submenu);
menu_model->AddSubMenu(
items[i].action + IDC_CONTENT_CONTEXT_CUSTOM_FIRST,
items[i].label,
submenu);
break;
}
default:
NOTREACHED();
break;
}
}
}
} // namespace
// static
const size_t RenderViewContextMenu::kMaxExtensionItemTitleLength = 75;
// static
const size_t RenderViewContextMenu::kMaxSelectionTextLength = 50;
// static
bool RenderViewContextMenu::IsDevToolsURL(const GURL& url) {
return url.SchemeIs(chrome::kChromeDevToolsScheme) &&
url.host() == chrome::kChromeUIDevToolsHost;
}
// static
bool RenderViewContextMenu::IsInternalResourcesURL(const GURL& url) {
if (!url.SchemeIs(chrome::kChromeUIScheme))
return false;
return url.host() == chrome::kChromeUISyncResourcesHost ||
url.host() == chrome::kChromeUIRemotingResourcesHost;
}
static const int kSpellcheckRadioGroup = 1;
RenderViewContextMenu::RenderViewContextMenu(
TabContents* tab_contents,
const ContextMenuParams& params)
: params_(params),
source_tab_contents_(tab_contents),
profile_(tab_contents->profile()),
ALLOW_THIS_IN_INITIALIZER_LIST(menu_model_(this)),
external_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(spellcheck_submenu_model_(this)),
ALLOW_THIS_IN_INITIALIZER_LIST(bidi_submenu_model_(this)) {
}
RenderViewContextMenu::~RenderViewContextMenu() {
}
// Menu construction functions -------------------------------------------------
void RenderViewContextMenu::Init() {
InitMenu();
PlatformInit();
}
static bool ExtensionContextMatch(const ContextMenuParams& params,
ExtensionMenuItem::ContextList contexts) {
bool has_link = !params.link_url.is_empty();
bool has_selection = !params.selection_text.empty();
bool in_frame = !params.frame_url.is_empty();
if (contexts.Contains(ExtensionMenuItem::ALL) ||
(has_selection && contexts.Contains(ExtensionMenuItem::SELECTION)) ||
(has_link && contexts.Contains(ExtensionMenuItem::LINK)) ||
(params.is_editable && contexts.Contains(ExtensionMenuItem::EDITABLE)) ||
(in_frame && contexts.Contains(ExtensionMenuItem::FRAME))) {
return true;
}
switch (params.media_type) {
case WebContextMenuData::MediaTypeImage:
return contexts.Contains(ExtensionMenuItem::IMAGE);
case WebContextMenuData::MediaTypeVideo:
return contexts.Contains(ExtensionMenuItem::VIDEO);
case WebContextMenuData::MediaTypeAudio:
return contexts.Contains(ExtensionMenuItem::AUDIO);
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 == WebContextMenuData::MediaTypeNone &&
contexts.Contains(ExtensionMenuItem::PAGE))
return true;
return false;
}
static bool ExtensionPatternMatch(const ExtensionExtent& patterns,
const GURL& url) {
// No patterns means no restriction, so that implicitly matches.
if (patterns.is_empty())
return true;
return patterns.ContainsURL(url);
}
static const GURL& GetDocumentURL(const ContextMenuParams& params) {
return params.frame_url.is_empty() ? params.page_url : params.frame_url;
}
// Given a list of items, returns the ones that match given the contents
// of |params| and the profile.
static ExtensionMenuItem::List GetRelevantExtensionItems(
const ExtensionMenuItem::List& items,
const ContextMenuParams& params,
Profile* profile,
bool can_cross_incognito) {
ExtensionMenuItem::List result;
for (ExtensionMenuItem::List::const_iterator i = items.begin();
i != items.end(); ++i) {
const ExtensionMenuItem* item = *i;
if (!ExtensionContextMatch(params, item->contexts()))
continue;
const GURL& document_url = GetDocumentURL(params);
if (!ExtensionPatternMatch(item->document_url_patterns(), document_url))
continue;
const GURL& target_url =
params.src_url.is_empty() ? params.link_url : params.src_url;
if (!ExtensionPatternMatch(item->target_url_patterns(), target_url))
continue;
if (item->id().profile == profile || can_cross_incognito)
result.push_back(*i);
}
return result;
}
void RenderViewContextMenu::AppendExtensionItems(
const std::string& extension_id, int* index) {
ExtensionService* service = profile_->GetExtensionService();
ExtensionMenuManager* 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 ExtensionMenuItem::List* all_items = manager->MenuItems(extension_id);
if (!all_items || all_items->empty())
return;
bool can_cross_incognito = service->CanCrossIncognito(extension);
ExtensionMenuItem::List items =
GetRelevantExtensionItems(*all_items, params_, profile_,
can_cross_incognito);
if (items.empty())
return;
// If this is the first extension-provided menu item, add a separator.
if (*index == 0)
menu_model_.AddSeparator();
int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
// Extensions 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.
string16 title;
ExtensionMenuItem::List submenu_items;
if (items.size() > 1 || items[0]->type() != ExtensionMenuItem::NORMAL) {
title = UTF8ToUTF16(extension->name());
submenu_items = items;
} else {
ExtensionMenuItem* item = items[0];
extension_item_map_[menu_id] = item->id();
title = item->TitleWithReplacement(PrintableSelectionText(),
kMaxExtensionItemTitleLength);
submenu_items = GetRelevantExtensionItems(item->children(), params_,
profile_, 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(this);
extension_menu_models_.push_back(submenu);
menu_model_.AddSubMenu(menu_id, title, submenu);
RecursivelyAppendExtensionItems(submenu_items, can_cross_incognito, submenu,
index);
}
SetExtensionIcon(extension_id);
}
void RenderViewContextMenu::RecursivelyAppendExtensionItems(
const ExtensionMenuItem::List& items,
bool can_cross_incognito,
ui::SimpleMenuModel* menu_model,
int *index) {
string16 selection_text = PrintableSelectionText();
ExtensionMenuItem::Type last_type = ExtensionMenuItem::NORMAL;
int radio_group_id = 1;
for (ExtensionMenuItem::List::const_iterator i = items.begin();
i != items.end(); ++i) {
ExtensionMenuItem* 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 == ExtensionMenuItem::RADIO &&
item->type() != ExtensionMenuItem::RADIO) {
menu_model->AddSeparator();
last_type = ExtensionMenuItem::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() == ExtensionMenuItem::NORMAL) {
ExtensionMenuItem::List children =
GetRelevantExtensionItems(item->children(), params_,
profile_, can_cross_incognito);
if (children.empty()) {
menu_model->AddItem(menu_id, title);
} else {
ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(this);
extension_menu_models_.push_back(submenu);
menu_model->AddSubMenu(menu_id, title, submenu);
RecursivelyAppendExtensionItems(children, can_cross_incognito,
submenu, index);
}
} else if (item->type() == ExtensionMenuItem::CHECKBOX) {
menu_model->AddCheckItem(menu_id, title);
} else if (item->type() == ExtensionMenuItem::RADIO) {
if (i != items.begin() &&
last_type != ExtensionMenuItem::RADIO) {
radio_group_id++;
// Auto-append a separator if needed.
if (last_type != ExtensionMenuItem::SEPARATOR)
menu_model->AddSeparator();
}
menu_model->AddRadioItem(menu_id, title, radio_group_id);
} else if (item->type() == ExtensionMenuItem::SEPARATOR) {
if (i != items.begin() && last_type != ExtensionMenuItem::SEPARATOR) {
menu_model->AddSeparator();
}
}
last_type = item->type();
}
}
void RenderViewContextMenu::SetExtensionIcon(const std::string& extension_id) {
ExtensionService* service = profile_->GetExtensionService();
ExtensionMenuManager* 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() == kFaviconSize);
DCHECK(icon.height() == kFaviconSize);
menu_model_.SetIcon(index, icon);
}
void RenderViewContextMenu::AppendAllExtensionItems() {
extension_item_map_.clear();
ExtensionService* service = profile_->GetExtensionService();
if (!service)
return; // In unit-tests, we may not have an ExtensionService.
ExtensionMenuManager* menu_manager = service->menu_manager();
const GURL& document_url = GetDocumentURL(params_);
if (!menu_manager->HasAllowedScheme(document_url))
return;
// Get a list of extension id's that have context menu items, and sort it by
// the extension's name.
std::set<std::string> ids = menu_manager->ExtensionIds();
std::vector<std::pair<std::string, std::string> > sorted_ids;
for (std::set<std::string>::iterator i = ids.begin(); i != ids.end(); ++i) {
const Extension* extension = service->GetExtensionById(*i, false);
if (extension)
sorted_ids.push_back(
std::pair<std::string, std::string>(extension->name(), *i));
}
// TODO(asargent) - See if this works properly for i18n names (bug 32363).
std::sort(sorted_ids.begin(), sorted_ids.end());
if (sorted_ids.empty())
return;
int index = 0;
base::TimeTicks begin = base::TimeTicks::Now();
std::vector<std::pair<std::string, std::string> >::const_iterator i;
for (i = sorted_ids.begin();
i != sorted_ids.end(); ++i) {
AppendExtensionItems(i->second, &index);
}
UMA_HISTOGRAM_TIMES("Extensions.ContextMenus_BuildTime",
base::TimeTicks::Now() - begin);
UMA_HISTOGRAM_COUNTS("Extensions.ContextMenus_ItemCount", index);
}
void RenderViewContextMenu::InitMenu() {
bool has_link = !params_.link_url.is_empty();
bool has_selection = !params_.selection_text.empty();
if (AppendCustomItems()) {
// Don't add items for Pepper menu.
if (!params_.custom_context.is_pepper_menu)
AppendDeveloperItems();
return;
}
// When no special node or text is selected and selection has no link,
// show page items.
bool is_devtools = false;
if (params_.media_type == WebContextMenuData::MediaTypeNone &&
!has_link &&
!params_.is_editable &&
!has_selection) {
if (!params_.page_url.is_empty()) {
is_devtools = IsDevToolsURL(params_.page_url);
if (!is_devtools && !IsInternalResourcesURL(params_.page_url)) {
AppendPageItems();
// Merge in frame items if we clicked within a frame that needs them.
if (!params_.frame_url.is_empty()) {
is_devtools = IsDevToolsURL(params_.frame_url);
if (!is_devtools && !IsInternalResourcesURL(params_.frame_url)) {
menu_model_.AddSeparator();
AppendFrameItems();
}
}
}
} else {
DCHECK(params_.frame_url.is_empty());
}
}
if (has_link) {
AppendLinkItems();
if (params_.media_type != WebContextMenuData::MediaTypeNone)
menu_model_.AddSeparator();
}
switch (params_.media_type) {
case WebContextMenuData::MediaTypeNone:
break;
case WebContextMenuData::MediaTypeImage:
AppendImageItems();
break;
case WebContextMenuData::MediaTypeVideo:
AppendVideoItems();
break;
case WebContextMenuData::MediaTypeAudio:
AppendAudioItems();
break;
case WebContextMenuData::MediaTypePlugin:
AppendPluginItems();
break;
#ifdef WEBCONTEXT_MEDIATYPEFILE_DEFINED
case WebContextMenuData::MediaTypeFile:
break;
#endif
}
if (params_.is_editable)
AppendEditableItems();
else if (has_selection)
AppendCopyItem();
if (has_selection)
AppendSearchProvider();
if (!is_devtools)
AppendAllExtensionItems();
AppendDeveloperItems();
}
void RenderViewContextMenu::LookUpInDictionary() {
// Used only in the Mac port.
NOTREACHED();
}
bool RenderViewContextMenu::AppendCustomItems() {
size_t total_items = 0;
AddCustomItemsToMenu(params_.custom_items, 0, &total_items, this,
&menu_model_);
return total_items > 0;
}
void RenderViewContextMenu::AppendDeveloperItems() {
// In the DevTools popup menu, "developer items" is normally the only
// section, so omit the separator there.
if (menu_model_.GetItemCount() > 0)
menu_model_.AddSeparator();
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_INSPECTELEMENT,
IDS_CONTENT_CONTEXT_INSPECTELEMENT);
}
void RenderViewContextMenu::AppendLinkItems() {
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB,
IDS_CONTENT_CONTEXT_OPENLINKNEWTAB);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW,
IDS_CONTENT_CONTEXT_OPENLINKNEWWINDOW);
if (!external_) {
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD,
IDS_CONTENT_CONTEXT_OPENLINKOFFTHERECORD);
}
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVELINKAS,
IDS_CONTENT_CONTEXT_SAVELINKAS);
menu_model_.AddItemWithStringId(
IDC_CONTENT_CONTEXT_COPYLINKLOCATION,
params_.link_url.SchemeIs(chrome::kMailToScheme) ?
IDS_CONTENT_CONTEXT_COPYEMAILADDRESS :
IDS_CONTENT_CONTEXT_COPYLINKLOCATION);
}
void RenderViewContextMenu::AppendImageItems() {
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVEIMAGEAS,
IDS_CONTENT_CONTEXT_SAVEIMAGEAS);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYIMAGELOCATION,
IDS_CONTENT_CONTEXT_COPYIMAGELOCATION);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYIMAGE,
IDS_CONTENT_CONTEXT_COPYIMAGE);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB,
IDS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
}
void RenderViewContextMenu::AppendAudioItems() {
AppendMediaItems();
menu_model_.AddSeparator();
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVEAVAS,
IDS_CONTENT_CONTEXT_SAVEAUDIOAS);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYAVLOCATION,
IDS_CONTENT_CONTEXT_COPYAUDIOLOCATION);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENAVNEWTAB,
IDS_CONTENT_CONTEXT_OPENAUDIONEWTAB);
}
void RenderViewContextMenu::AppendVideoItems() {
AppendMediaItems();
menu_model_.AddSeparator();
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVEAVAS,
IDS_CONTENT_CONTEXT_SAVEVIDEOAS);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYAVLOCATION,
IDS_CONTENT_CONTEXT_COPYVIDEOLOCATION);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENAVNEWTAB,
IDS_CONTENT_CONTEXT_OPENVIDEONEWTAB);
}
void RenderViewContextMenu::AppendMediaItems() {
int media_flags = params_.media_flags;
menu_model_.AddItemWithStringId(
IDC_CONTENT_CONTEXT_PLAYPAUSE,
media_flags & WebContextMenuData::MediaPaused ?
IDS_CONTENT_CONTEXT_PLAY :
IDS_CONTENT_CONTEXT_PAUSE);
menu_model_.AddItemWithStringId(
IDC_CONTENT_CONTEXT_MUTE,
media_flags & WebContextMenuData::MediaMuted ?
IDS_CONTENT_CONTEXT_UNMUTE :
IDS_CONTENT_CONTEXT_MUTE);
menu_model_.AddCheckItemWithStringId(IDC_CONTENT_CONTEXT_LOOP,
IDS_CONTENT_CONTEXT_LOOP);
menu_model_.AddCheckItemWithStringId(IDC_CONTENT_CONTEXT_CONTROLS,
IDS_CONTENT_CONTEXT_CONTROLS);
}
void RenderViewContextMenu::AppendPluginItems() {
if (params_.page_url == params_.src_url) {
// Full page plugin, so show page menu items.
if (params_.link_url.is_empty() && params_.selection_text.empty())
AppendPageItems();
} else {
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVEAVAS,
IDS_CONTENT_CONTEXT_SAVEPAGEAS);
menu_model_.AddItemWithStringId(IDC_PRINT, IDS_CONTENT_CONTEXT_PRINT);
}
}
void RenderViewContextMenu::AppendPageItems() {
menu_model_.AddItemWithStringId(IDC_BACK, IDS_CONTENT_CONTEXT_BACK);
menu_model_.AddItemWithStringId(IDC_FORWARD, IDS_CONTENT_CONTEXT_FORWARD);
menu_model_.AddItemWithStringId(IDC_RELOAD, IDS_CONTENT_CONTEXT_RELOAD);
menu_model_.AddSeparator();
menu_model_.AddItemWithStringId(IDC_SAVE_PAGE,
IDS_CONTENT_CONTEXT_SAVEPAGEAS);
menu_model_.AddItemWithStringId(IDC_PRINT, IDS_CONTENT_CONTEXT_PRINT);
std::string locale = g_browser_process->GetApplicationLocale();
locale = TranslateManager::GetLanguageCode(locale);
string16 language = l10n_util::GetDisplayNameForLocale(locale, locale, true);
menu_model_.AddItem(
IDC_CONTENT_CONTEXT_TRANSLATE,
l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_TRANSLATE, language));
menu_model_.AddItemWithStringId(IDC_VIEW_SOURCE,
IDS_CONTENT_CONTEXT_VIEWPAGESOURCE);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_VIEWPAGEINFO,
IDS_CONTENT_CONTEXT_VIEWPAGEINFO);
}
void RenderViewContextMenu::AppendFrameItems() {
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_RELOADFRAME,
IDS_CONTENT_CONTEXT_RELOADFRAME);
// These two menu items have yet to be implemented.
// http://code.google.com/p/chromium/issues/detail?id=11827
// IDS_CONTENT_CONTEXT_SAVEFRAMEAS
// IDS_CONTENT_CONTEXT_PRINTFRAME
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE,
IDS_CONTENT_CONTEXT_VIEWFRAMESOURCE);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_VIEWFRAMEINFO,
IDS_CONTENT_CONTEXT_VIEWFRAMEINFO);
}
void RenderViewContextMenu::AppendCopyItem() {
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPY,
IDS_CONTENT_CONTEXT_COPY);
}
void RenderViewContextMenu::AppendSearchProvider() {
DCHECK(profile_);
TrimWhitespace(params_.selection_text, TRIM_ALL, ¶ms_.selection_text);
if (params_.selection_text.empty())
return;
AutocompleteMatch match;
profile_->GetAutocompleteClassifier()->Classify(
params_.selection_text, string16(), false, &match, NULL);
selection_navigation_url_ = match.destination_url;
if (!selection_navigation_url_.is_valid())
return;
string16 printable_selection_text = PrintableSelectionText();
// Escape "&" as "&&".
for (size_t i = printable_selection_text.find('&'); i != string16::npos;
i = printable_selection_text.find('&', i + 2))
printable_selection_text.insert(i, 1, '&');
if (match.transition == PageTransition::TYPED) {
if (ChildProcessSecurityPolicy::GetInstance()->IsWebSafeScheme(
selection_navigation_url_.scheme())) {
menu_model_.AddItem(
IDC_CONTENT_CONTEXT_GOTOURL,
l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_GOTOURL,
printable_selection_text));
}
} else {
const TemplateURL* const default_provider =
profile_->GetTemplateURLModel()->GetDefaultSearchProvider();
if (!default_provider)
return;
menu_model_.AddItem(
IDC_CONTENT_CONTEXT_SEARCHWEBFOR,
l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_SEARCHWEBFOR,
default_provider->short_name(),
printable_selection_text));
}
}
void RenderViewContextMenu::AppendEditableItems() {
// Append Dictionary spell check suggestions.
for (size_t i = 0; i < params_.dictionary_suggestions.size() &&
IDC_SPELLCHECK_SUGGESTION_0 + i <= IDC_SPELLCHECK_SUGGESTION_LAST;
++i) {
menu_model_.AddItem(IDC_SPELLCHECK_SUGGESTION_0 + static_cast<int>(i),
params_.dictionary_suggestions[i]);
}
if (!params_.dictionary_suggestions.empty())
menu_model_.AddSeparator();
// If word is misspelled, give option for "Add to dictionary"
if (!params_.misspelled_word.empty()) {
if (params_.dictionary_suggestions.empty()) {
menu_model_.AddItem(0,
l10n_util::GetStringUTF16(
IDS_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS));
}
menu_model_.AddItemWithStringId(IDC_SPELLCHECK_ADD_TO_DICTIONARY,
IDS_CONTENT_CONTEXT_ADD_TO_DICTIONARY);
menu_model_.AddSeparator();
}
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_UNDO,
IDS_CONTENT_CONTEXT_UNDO);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_REDO,
IDS_CONTENT_CONTEXT_REDO);
menu_model_.AddSeparator();
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_CUT,
IDS_CONTENT_CONTEXT_CUT);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPY,
IDS_CONTENT_CONTEXT_COPY);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_PASTE,
IDS_CONTENT_CONTEXT_PASTE);
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_DELETE,
IDS_CONTENT_CONTEXT_DELETE);
menu_model_.AddSeparator();
AppendSpellcheckOptionsSubMenu();
#if defined(OS_MACOSX)
// OS X provides a contextual menu to set writing direction for BiDi
// languages.
// This functionality is exposed as a keyboard shortcut on Windows & Linux.
AppendBidiSubMenu();
#endif // OS_MACOSX
menu_model_.AddSeparator();
menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SELECTALL,
IDS_CONTENT_CONTEXT_SELECTALL);
}
void RenderViewContextMenu::AppendSpellcheckOptionsSubMenu() {
// Add Spell Check languages to sub menu.
std::vector<std::string> spellcheck_languages;
SpellCheckHost::GetSpellCheckLanguages(profile_,
&spellcheck_languages);
DCHECK(spellcheck_languages.size() <
IDC_SPELLCHECK_LANGUAGES_LAST - IDC_SPELLCHECK_LANGUAGES_FIRST);
const std::string app_locale = g_browser_process->GetApplicationLocale();
for (size_t i = 0; i < spellcheck_languages.size(); ++i) {
string16 display_name(l10n_util::GetDisplayNameForLocale(
spellcheck_languages[i], app_locale, true));
spellcheck_submenu_model_.AddRadioItem(
IDC_SPELLCHECK_LANGUAGES_FIRST + i,
display_name,
kSpellcheckRadioGroup);
}
// Add item in the sub menu to pop up the fonts and languages options menu.
spellcheck_submenu_model_.AddSeparator();
spellcheck_submenu_model_.AddItemWithStringId(
IDC_CONTENT_CONTEXT_LANGUAGE_SETTINGS,
IDS_CONTENT_CONTEXT_LANGUAGE_SETTINGS);
// Add 'Check the spelling of this field' item in the sub menu.
spellcheck_submenu_model_.AddCheckItem(
IDC_CHECK_SPELLING_OF_THIS_FIELD,
l10n_util::GetStringUTF16(
IDS_CONTENT_CONTEXT_CHECK_SPELLING_OF_THIS_FIELD));
// Add option for showing the spelling panel if the platform spellchecker
// supports it.
if (SpellCheckerPlatform::SpellCheckerAvailable() &&
SpellCheckerPlatform::SpellCheckerProvidesPanel()) {
spellcheck_submenu_model_.AddCheckItem(
IDC_SPELLPANEL_TOGGLE,
l10n_util::GetStringUTF16(
SpellCheckerPlatform::SpellingPanelVisible() ?
IDS_CONTENT_CONTEXT_HIDE_SPELLING_PANEL :
IDS_CONTENT_CONTEXT_SHOW_SPELLING_PANEL));
}
menu_model_.AddSubMenu(
IDC_SPELLCHECK_MENU,
l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_SPELLCHECK_MENU),
&spellcheck_submenu_model_);
}
#if defined(OS_MACOSX)
void RenderViewContextMenu::AppendBidiSubMenu() {
bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_DEFAULT,
l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_DEFAULT));
bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_LTR,
l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_LTR));
bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_RTL,
l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_RTL));
menu_model_.AddSubMenu(
IDC_WRITING_DIRECTION_MENU,
l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_MENU),
&bidi_submenu_model_);
}
#endif // OS_MACOSX
ExtensionMenuItem* RenderViewContextMenu::GetExtensionMenuItem(int id) const {
ExtensionMenuManager* manager =
profile_->GetExtensionService()->menu_manager();
std::map<int, ExtensionMenuItem::Id>::const_iterator i =
extension_item_map_.find(id);
if (i != extension_item_map_.end()) {
ExtensionMenuItem* item = manager->GetItemById(i->second);
if (item)
return item;
}
return NULL;
}
// Menu delegate functions -----------------------------------------------------
bool RenderViewContextMenu::IsCommandIdEnabled(int id) const {
if (id == IDC_PRINT &&
(source_tab_contents_->content_restrictions() &
CONTENT_RESTRICTION_PRINT)) {
return false;
}
if (id == IDC_SAVE_PAGE &&
(source_tab_contents_->content_restrictions() &
CONTENT_RESTRICTION_SAVE)) {
return false;
}
// Allow Spell Check language items on sub menu for text area context menu.
if ((id >= IDC_SPELLCHECK_LANGUAGES_FIRST) &&
(id < IDC_SPELLCHECK_LANGUAGES_LAST)) {
return profile_->GetPrefs()->GetBoolean(prefs::kEnableSpellCheck);
}
// Custom items.
if (id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST) {
return IsCustomItemEnabled(params_.custom_items, id);
}
// Extension items.
if (id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
// In the future we may add APIs for extensions to disable items, but for
// now all items are implicitly enabled.
return true;
}
switch (id) {
case IDC_BACK:
return source_tab_contents_->controller().CanGoBack();
case IDC_FORWARD:
return source_tab_contents_->controller().CanGoForward();
case IDC_RELOAD:
return source_tab_contents_->delegate() &&
source_tab_contents_->delegate()->CanReloadContents(
source_tab_contents_);
case IDC_VIEW_SOURCE:
case IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE:
return source_tab_contents_->controller().CanViewSource();
case IDC_CONTENT_CONTEXT_INSPECTELEMENT:
// Viewing page info is not a developer command but is meaningful for the
// same set of pages which developer commands are meaningful for.
case IDC_CONTENT_CONTEXT_VIEWPAGEINFO:
return IsDevCommandEnabled(id);
case IDC_CONTENT_CONTEXT_TRANSLATE: {
TranslateTabHelper* helper =
TabContentsWrapper::GetCurrentWrapperForContents(
source_tab_contents_)->translate_tab_helper();
std::string original_lang =
helper->language_state().original_language();
std::string target_lang = g_browser_process->GetApplicationLocale();
target_lang = TranslateManager::GetLanguageCode(target_lang);
// Note that we intentionally enable the menu even if the original and
// target languages are identical. This is to give a way to user to
// translate a page that might contains text fragments in a different
// language.
return !!(params_.edit_flags & WebContextMenuData::CanTranslate) &&
helper->language_state().page_translatable() &&
!original_lang.empty() && // Did we receive the page language yet?
// Only allow translating languages we explitly support and the
// unknown language (in which case the page language is detected on
// the server side).
(original_lang == chrome::kUnknownLanguageCode ||
TranslateManager::IsSupportedLanguage(original_lang)) &&
!helper->language_state().IsPageTranslated() &&
!source_tab_contents_->interstitial_page() &&
TranslateManager::IsTranslatableURL(params_.page_url);
}
case IDC_CONTENT_CONTEXT_OPENLINKNEWTAB:
case IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW:
return params_.link_url.is_valid();
case IDC_CONTENT_CONTEXT_COPYLINKLOCATION:
return params_.unfiltered_link_url.is_valid();
case IDC_CONTENT_CONTEXT_SAVELINKAS:
return params_.link_url.is_valid() &&
net::URLRequest::IsHandledURL(params_.link_url);
case IDC_CONTENT_CONTEXT_SAVEIMAGEAS:
return params_.src_url.is_valid() &&
net::URLRequest::IsHandledURL(params_.src_url);
case IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB:
// The images shown in the most visited thumbnails do not currently open
// in a new tab as they should. Disabling this context menu option for
// now, as a quick hack, before we resolve this issue (Issue = 2608).
// TODO(sidchat): Enable this option once this issue is resolved.
if (params_.src_url.scheme() == chrome::kChromeUIScheme ||
!params_.src_url.is_valid())
return false;
return true;
case IDC_CONTENT_CONTEXT_COPYIMAGE:
return !params_.is_image_blocked;
// Media control commands should all be disabled if the player is in an
// error state.
case IDC_CONTENT_CONTEXT_PLAYPAUSE:
case IDC_CONTENT_CONTEXT_LOOP:
return (params_.media_flags &
WebContextMenuData::MediaInError) == 0;
// Mute and unmute should also be disabled if the player has no audio.
case IDC_CONTENT_CONTEXT_MUTE:
return (params_.media_flags &
WebContextMenuData::MediaHasAudio) != 0 &&
(params_.media_flags &
WebContextMenuData::MediaInError) == 0;
// Media controls can be toggled only for video player. If we toggle
// controls for audio then the player disappears, and there is no way to
// return it back.
case IDC_CONTENT_CONTEXT_CONTROLS:
return (params_.media_flags &
WebContextMenuData::MediaHasVideo) != 0;
case IDC_CONTENT_CONTEXT_COPYAVLOCATION:
case IDC_CONTENT_CONTEXT_COPYIMAGELOCATION:
return params_.src_url.is_valid();
case IDC_CONTENT_CONTEXT_SAVEAVAS:
return (params_.media_flags &
WebContextMenuData::MediaCanSave) &&
params_.src_url.is_valid() &&
net::URLRequest::IsHandledURL(params_.src_url);
case IDC_CONTENT_CONTEXT_OPENAVNEWTAB:
return true;
case IDC_SAVE_PAGE: {
// Instead of using GetURL here, we use url() (which is the "real" url of
// the page) from the NavigationEntry because its reflects their origin
// rather than the display one (returned by GetURL) which may be
// different (like having "view-source:" on the front).
NavigationEntry* active_entry =
source_tab_contents_->controller().GetActiveEntry();
return SavePackage::IsSavableURL(
(active_entry) ? active_entry->url() : GURL());
}
case IDC_CONTENT_CONTEXT_RELOADFRAME:
return params_.frame_url.is_valid();
case IDC_CONTENT_CONTEXT_UNDO:
return !!(params_.edit_flags & WebContextMenuData::CanUndo);
case IDC_CONTENT_CONTEXT_REDO:
return !!(params_.edit_flags & WebContextMenuData::CanRedo);
case IDC_CONTENT_CONTEXT_CUT:
return !!(params_.edit_flags & WebContextMenuData::CanCut);
case IDC_CONTENT_CONTEXT_COPY:
return !!(params_.edit_flags & WebContextMenuData::CanCopy);
case IDC_CONTENT_CONTEXT_PASTE:
return !!(params_.edit_flags & WebContextMenuData::CanPaste);
case IDC_CONTENT_CONTEXT_DELETE:
return !!(params_.edit_flags & WebContextMenuData::CanDelete);
case IDC_CONTENT_CONTEXT_SELECTALL:
return !!(params_.edit_flags & WebContextMenuData::CanSelectAll);
case IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD:
return !profile_->IsOffTheRecord() && params_.link_url.is_valid() &&
profile_->GetPrefs()->GetBoolean(prefs::kIncognitoEnabled);
case IDC_SPELLCHECK_ADD_TO_DICTIONARY:
return !params_.misspelled_word.empty();
case IDC_PRINT:
if (g_browser_process->local_state() &&
!g_browser_process->local_state()->GetBoolean(
prefs::kPrintingEnabled)) {
return false;
}
return params_.media_type == WebContextMenuData::MediaTypeNone ||
params_.media_flags & WebContextMenuData::MediaCanPrint;
case IDC_CONTENT_CONTEXT_SEARCHWEBFOR:
case IDC_CONTENT_CONTEXT_GOTOURL:
case IDC_SPELLCHECK_SUGGESTION_0:
case IDC_SPELLCHECK_SUGGESTION_1:
case IDC_SPELLCHECK_SUGGESTION_2:
case IDC_SPELLCHECK_SUGGESTION_3:
case IDC_SPELLCHECK_SUGGESTION_4:
case IDC_SPELLPANEL_TOGGLE:
#if !defined(OS_MACOSX)
// TODO(jeremy): re-enable - http://crbug.com/34512 .
case IDC_CONTENT_CONTEXT_LANGUAGE_SETTINGS:
#endif
case IDC_CONTENT_CONTEXT_VIEWFRAMEINFO:
return true;
case IDC_CHECK_SPELLING_OF_THIS_FIELD:
return profile_->GetPrefs()->GetBoolean(prefs::kEnableSpellCheck);
#if defined(OS_MACOSX)
// TODO(jeremy): re-enable - http://crbug.com/34512 .
case IDC_CONTENT_CONTEXT_LANGUAGE_SETTINGS:
return false;
#endif
#if defined(OS_MACOSX)
case IDC_WRITING_DIRECTION_DEFAULT: // Provided to match OS defaults.
return params_.writing_direction_default &
WebContextMenuData::CheckableMenuItemEnabled;
case IDC_WRITING_DIRECTION_RTL:
return params_.writing_direction_right_to_left &
WebContextMenuData::CheckableMenuItemEnabled;
case IDC_WRITING_DIRECTION_LTR:
return params_.writing_direction_left_to_right &
WebContextMenuData::CheckableMenuItemEnabled;
case IDC_WRITING_DIRECTION_MENU:
return true;
case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY:
// This is OK because the menu is not shown when it isn't
// appropriate.
return true;
#elif defined(OS_POSIX)
// TODO(suzhe): this should not be enabled for password fields.
case IDC_INPUT_METHODS_MENU:
return true;
#endif
case IDC_SPELLCHECK_MENU:
return true;
default:
NOTREACHED();
return false;
}
}
bool RenderViewContextMenu::IsCommandIdChecked(int id) const {
// See if the video is set to looping.
if (id == IDC_CONTENT_CONTEXT_LOOP) {
return (params_.media_flags &
WebContextMenuData::MediaLoop) != 0;
}
if (id == IDC_CONTENT_CONTEXT_CONTROLS) {
return (params_.media_flags &
WebContextMenuData::MediaControlRootElement) != 0;
}
// Custom items.
if (id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST) {
return IsCustomItemChecked(params_.custom_items, id);
}
// Extension items.
if (id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
ExtensionMenuItem* item = GetExtensionMenuItem(id);
if (item)
return item->checked();
else
return false;
}
#if defined(OS_MACOSX)
if (id == IDC_WRITING_DIRECTION_DEFAULT)
return params_.writing_direction_default &
WebContextMenuData::CheckableMenuItemChecked;
if (id == IDC_WRITING_DIRECTION_RTL)
return params_.writing_direction_right_to_left &
WebContextMenuData::CheckableMenuItemChecked;
if (id == IDC_WRITING_DIRECTION_LTR)
return params_.writing_direction_left_to_right &
WebContextMenuData::CheckableMenuItemChecked;
if (id == IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY)
return false;
#endif // OS_MACOSX
// Check box for 'Check the Spelling of this field'.
if (id == IDC_CHECK_SPELLING_OF_THIS_FIELD) {
return (params_.spellcheck_enabled &&
profile_->GetPrefs()->GetBoolean(prefs::kEnableSpellCheck));
}
// Don't bother getting the display language vector if this isn't a spellcheck
// language.
if ((id < IDC_SPELLCHECK_LANGUAGES_FIRST) ||
(id >= IDC_SPELLCHECK_LANGUAGES_LAST))
return false;
std::vector<std::string> languages;
return SpellCheckHost::GetSpellCheckLanguages(profile_, &languages) ==
(id - IDC_SPELLCHECK_LANGUAGES_FIRST);
}
void RenderViewContextMenu::ExecuteCommand(int id) {
// Check to see if one of the spell check language ids have been clicked.
if (id >= IDC_SPELLCHECK_LANGUAGES_FIRST &&
id < IDC_SPELLCHECK_LANGUAGES_LAST) {
const size_t language_number = id - IDC_SPELLCHECK_LANGUAGES_FIRST;
std::vector<std::string> languages;
SpellCheckHost::GetSpellCheckLanguages(profile_, &languages);
if (language_number < languages.size()) {
StringPrefMember dictionary_language;
dictionary_language.Init(prefs::kSpellCheckDictionary,
profile_->GetPrefs(), NULL);
dictionary_language.SetValue(languages[language_number]);
}
return;
}
// Process custom actions range.
if (id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST) {
unsigned action = id - IDC_CONTENT_CONTEXT_CUSTOM_FIRST;
source_tab_contents_->render_view_host()->PerformCustomContextMenuAction(
params_.custom_context, action);
return;
}
// Process extension menu items.
if (id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
ExtensionMenuManager* manager =
profile_->GetExtensionService()->menu_manager();
std::map<int, ExtensionMenuItem::Id>::const_iterator i =
extension_item_map_.find(id);
if (i != extension_item_map_.end()) {
manager->ExecuteCommand(profile_, source_tab_contents_, params_,
i->second);
}
return;
}
switch (id) {
case IDC_CONTENT_CONTEXT_OPENLINKNEWTAB:
OpenURL(params_.link_url,
source_tab_contents_->delegate() &&
source_tab_contents_->delegate()->IsApplication() ?
NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB,
PageTransition::LINK);
break;
case IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW:
OpenURL(params_.link_url, NEW_WINDOW, PageTransition::LINK);
break;
case IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD:
OpenURL(params_.link_url, OFF_THE_RECORD, PageTransition::LINK);
break;
case IDC_CONTENT_CONTEXT_SAVEAVAS:
case IDC_CONTENT_CONTEXT_SAVEIMAGEAS:
case IDC_CONTENT_CONTEXT_SAVELINKAS: {
download_util::RecordDownloadCount(
download_util::INITIATED_BY_CONTEXT_MENU_COUNT);
const GURL& referrer =
params_.frame_url.is_empty() ? params_.page_url : params_.frame_url;
const GURL& url =
(id == IDC_CONTENT_CONTEXT_SAVELINKAS ? params_.link_url :
params_.src_url);
DownloadManager* dlm = profile_->GetDownloadManager();
dlm->DownloadUrl(url, referrer, params_.frame_charset,
source_tab_contents_);
break;
}
case IDC_CONTENT_CONTEXT_COPYLINKLOCATION:
WriteURLToClipboard(params_.unfiltered_link_url);
break;
case IDC_CONTENT_CONTEXT_COPYIMAGELOCATION:
case IDC_CONTENT_CONTEXT_COPYAVLOCATION:
WriteURLToClipboard(params_.src_url);
break;
case IDC_CONTENT_CONTEXT_COPYIMAGE:
CopyImageAt(params_.x, params_.y);
break;
case IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB:
case IDC_CONTENT_CONTEXT_OPENAVNEWTAB:
OpenURL(params_.src_url, NEW_BACKGROUND_TAB, PageTransition::LINK);
break;
case IDC_CONTENT_CONTEXT_PLAYPAUSE: {
bool play = !!(params_.media_flags & WebContextMenuData::MediaPaused);
if (play) {
UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Play"),
profile_);
} else {
UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Pause"),
profile_);
}
MediaPlayerActionAt(gfx::Point(params_.x, params_.y),
WebMediaPlayerAction(
WebMediaPlayerAction::Play, play));
break;
}
case IDC_CONTENT_CONTEXT_MUTE: {
bool mute = !(params_.media_flags & WebContextMenuData::MediaMuted);
if (mute) {
UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Mute"),
profile_);
} else {
UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Unmute"),
profile_);
}
MediaPlayerActionAt(gfx::Point(params_.x, params_.y),
WebMediaPlayerAction(
WebMediaPlayerAction::Mute, mute));
break;
}
case IDC_CONTENT_CONTEXT_LOOP:
UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Loop"),
profile_);
MediaPlayerActionAt(gfx::Point(params_.x, params_.y),
WebMediaPlayerAction(
WebMediaPlayerAction::Loop,
!IsCommandIdChecked(IDC_CONTENT_CONTEXT_LOOP)));
break;
case IDC_CONTENT_CONTEXT_CONTROLS:
UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Controls"),
profile_);
MediaPlayerActionAt(
gfx::Point(params_.x, params_.y),
WebMediaPlayerAction(
WebMediaPlayerAction::Controls,
!IsCommandIdChecked(IDC_CONTENT_CONTEXT_CONTROLS)));
break;
case IDC_BACK:
source_tab_contents_->controller().GoBack();
break;
case IDC_FORWARD:
source_tab_contents_->controller().GoForward();
break;
case IDC_SAVE_PAGE: {
TabContentsWrapper* wrapper =
TabContentsWrapper::GetCurrentWrapperForContents(
source_tab_contents_);
wrapper->download_tab_helper()->OnSavePage();
break;
}
case IDC_RELOAD:
// Prevent the modal "Resubmit form post" dialog from appearing in the
// context of an external context menu.
source_tab_contents_->controller().Reload(!external_);
break;
case IDC_PRINT:
if (params_.media_type == WebContextMenuData::MediaTypeNone) {
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnablePrintPreview)) {
printing::PrintPreviewTabController::PrintPreview(
source_tab_contents_);
} else {
TabContentsWrapper* wrapper =
TabContentsWrapper::GetCurrentWrapperForContents(
source_tab_contents_);
wrapper->print_view_manager()->PrintNow();
}
} else {
RenderViewHost* rvh = source_tab_contents_->render_view_host();
rvh->Send(new PrintMsg_PrintNodeUnderContextMenu(rvh->routing_id()));
}
break;
case IDC_VIEW_SOURCE:
source_tab_contents_->ViewSource();
break;
case IDC_CONTENT_CONTEXT_INSPECTELEMENT:
Inspect(params_.x, params_.y);
break;
case IDC_CONTENT_CONTEXT_VIEWPAGEINFO: {
NavigationEntry* nav_entry =
source_tab_contents_->controller().GetActiveEntry();
source_tab_contents_->ShowPageInfo(nav_entry->url(), nav_entry->ssl(),
true);
break;
}
case IDC_CONTENT_CONTEXT_TRANSLATE: {
// A translation might have been triggered by the time the menu got
// selected, do nothing in that case.
TranslateTabHelper* helper =
TabContentsWrapper::GetCurrentWrapperForContents(
source_tab_contents_)->translate_tab_helper();
if (helper->language_state().IsPageTranslated() ||
helper->language_state().translation_pending()) {
return;
}
std::string original_lang = helper->language_state().original_language();
std::string target_lang = g_browser_process->GetApplicationLocale();
target_lang = TranslateManager::GetLanguageCode(target_lang);
// Since the user decided to translate for that language and site, clears
// any preferences for not translating them.
TranslatePrefs prefs(profile_->GetPrefs());
prefs.RemoveLanguageFromBlacklist(original_lang);
prefs.RemoveSiteFromBlacklist(params_.page_url.HostNoBrackets());
TranslateManager::GetInstance()->TranslatePage(
source_tab_contents_, original_lang, target_lang);
break;
}
case IDC_CONTENT_CONTEXT_RELOADFRAME:
source_tab_contents_->render_view_host()->ReloadFrame();
break;
case IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE:
source_tab_contents_->ViewFrameSource(params_.frame_url,
params_.frame_content_state);
break;
case IDC_CONTENT_CONTEXT_VIEWFRAMEINFO: {
// Deserialize the SSL info.
NavigationEntry::SSLStatus ssl;
if (!params_.security_info.empty()) {
int cert_id, cert_status, security_bits, connection_status;
SSLManager::DeserializeSecurityInfo(params_.security_info,
&cert_id,
&cert_status,
&security_bits,
&connection_status);
ssl.set_cert_id(cert_id);
ssl.set_cert_status(cert_status);
ssl.set_security_bits(security_bits);
ssl.set_connection_status(connection_status);
}
source_tab_contents_->ShowPageInfo(params_.frame_url, ssl,
false); // Don't show the history.
break;
}
case IDC_CONTENT_CONTEXT_UNDO:
source_tab_contents_->render_view_host()->Undo();
break;
case IDC_CONTENT_CONTEXT_REDO:
source_tab_contents_->render_view_host()->Redo();
break;
case IDC_CONTENT_CONTEXT_CUT:
source_tab_contents_->render_view_host()->Cut();
break;
case IDC_CONTENT_CONTEXT_COPY:
source_tab_contents_->render_view_host()->Copy();
break;
case IDC_CONTENT_CONTEXT_PASTE:
source_tab_contents_->render_view_host()->Paste();
break;
case IDC_CONTENT_CONTEXT_DELETE:
source_tab_contents_->render_view_host()->Delete();
break;
case IDC_CONTENT_CONTEXT_SELECTALL:
source_tab_contents_->render_view_host()->SelectAll();
break;
case IDC_CONTENT_CONTEXT_SEARCHWEBFOR:
case IDC_CONTENT_CONTEXT_GOTOURL: {
OpenURL(selection_navigation_url_, NEW_FOREGROUND_TAB,
PageTransition::LINK);
break;
}
case IDC_SPELLCHECK_SUGGESTION_0:
case IDC_SPELLCHECK_SUGGESTION_1:
case IDC_SPELLCHECK_SUGGESTION_2:
case IDC_SPELLCHECK_SUGGESTION_3:
case IDC_SPELLCHECK_SUGGESTION_4:
source_tab_contents_->render_view_host()->Replace(
params_.dictionary_suggestions[id - IDC_SPELLCHECK_SUGGESTION_0]);
break;
case IDC_CHECK_SPELLING_OF_THIS_FIELD:
source_tab_contents_->render_view_host()->ToggleSpellCheck();
break;
case IDC_SPELLCHECK_ADD_TO_DICTIONARY: {
SpellCheckHost* spellcheck_host = profile_->GetSpellCheckHost();
if (!spellcheck_host) {
NOTREACHED();
break;
}
spellcheck_host->AddWord(UTF16ToUTF8(params_.misspelled_word));
SpellCheckerPlatform::AddWord(params_.misspelled_word);
break;
}
case IDC_CONTENT_CONTEXT_LANGUAGE_SETTINGS: {
std::string url = std::string(chrome::kChromeUISettingsURL) +
chrome::kLanguageOptionsSubPage;
OpenURL(GURL(url), NEW_FOREGROUND_TAB, PageTransition::LINK);
break;
}
case IDC_SPELLPANEL_TOGGLE:
source_tab_contents_->render_view_host()->ToggleSpellPanel(
SpellCheckerPlatform::SpellingPanelVisible());
break;
#if defined(OS_MACOSX)
case IDC_WRITING_DIRECTION_DEFAULT:
// WebKit's current behavior is for this menu item to always be disabled.
NOTREACHED();
break;
case IDC_WRITING_DIRECTION_RTL:
case IDC_WRITING_DIRECTION_LTR: {
WebKit::WebTextDirection dir = WebKit::WebTextDirectionLeftToRight;
if (id == IDC_WRITING_DIRECTION_RTL)
dir = WebKit::WebTextDirectionRightToLeft;
source_tab_contents_->render_view_host()->UpdateTextDirection(dir);
source_tab_contents_->render_view_host()->NotifyTextDirection();
break;
}
case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY:
LookUpInDictionary();
break;
#endif // OS_MACOSX
default:
NOTREACHED();
break;
}
}
void RenderViewContextMenu::MenuWillShow() {
RenderWidgetHostView* view = source_tab_contents_->GetRenderWidgetHostView();
if (view)
view->ShowingContextMenu(true);
}
void RenderViewContextMenu::MenuClosed() {
RenderWidgetHostView* view = source_tab_contents_->GetRenderWidgetHostView();
if (view)
view->ShowingContextMenu(false);
if (source_tab_contents_->render_view_host()) {
source_tab_contents_->render_view_host()->ContextMenuClosed(
params_.custom_context);
}
}
bool RenderViewContextMenu::IsDevCommandEnabled(int id) const {
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kAlwaysEnableDevTools))
return true;
NavigationEntry *active_entry =
source_tab_contents_->controller().GetActiveEntry();
if (!active_entry)
return false;
// Don't inspect view source.
if (active_entry->IsViewSourceMode())
return false;
// Don't inspect about:network, about:memory, etc.
// However, we do want to inspect about:blank, which is often
// used by ordinary web pages.
if (active_entry->virtual_url().SchemeIs(chrome::kAboutScheme) &&
!LowerCaseEqualsASCII(active_entry->virtual_url().path(), "blank"))
return false;
if (id == IDC_CONTENT_CONTEXT_INSPECTELEMENT) {
// Don't enable the web inspector if JavaScript is disabled.
if (!profile_->GetPrefs()->GetBoolean(prefs::kWebKitJavascriptEnabled) ||
command_line.HasSwitch(switches::kDisableJavaScript))
return false;
// Don't enable the web inspector if the developer tools are disabled via
// the preference dev-tools-disabled.
if (profile_->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled))
return false;
}
return true;
}
string16 RenderViewContextMenu::PrintableSelectionText() {
return l10n_util::TruncateString(params_.selection_text,
kMaxSelectionTextLength);
}
// Controller functions --------------------------------------------------------
void RenderViewContextMenu::OpenURL(
const GURL& url,
WindowOpenDisposition disposition,
PageTransition::Type transition) {
source_tab_contents_->OpenURL(url, GURL(), disposition, transition);
}
void RenderViewContextMenu::CopyImageAt(int x, int y) {
source_tab_contents_->render_view_host()->CopyImageAt(x, y);
}
void RenderViewContextMenu::Inspect(int x, int y) {
UserMetrics::RecordAction(UserMetricsAction("DevTools_InspectElement"),
profile_);
DevToolsManager::GetInstance()->InspectElement(
source_tab_contents_->render_view_host(), x, y);
}
void RenderViewContextMenu::WriteURLToClipboard(const GURL& url) {
chrome_browser_net::WriteURLToClipboard(
url,
profile_->GetPrefs()->GetString(prefs::kAcceptLanguages),
g_browser_process->clipboard());
}
void RenderViewContextMenu::MediaPlayerActionAt(
const gfx::Point& location,
const WebMediaPlayerAction& action) {
source_tab_contents_->render_view_host()->MediaPlayerActionAt(
location, action);
}