render_view_context_menu.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 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 <algorithm>
6#include <set>
7
8#include "chrome/browser/tab_contents/render_view_context_menu.h"
9
10#include "base/command_line.h"
11#include "base/logging.h"
12#include "base/metrics/histogram.h"
13#include "base/stl_util-inl.h"
14#include "base/string_util.h"
15#include "base/time.h"
16#include "base/utf_string_conversions.h"
17#include "chrome/app/chrome_command_ids.h"
18#include "chrome/browser/autocomplete/autocomplete_classifier.h"
19#include "chrome/browser/autocomplete/autocomplete_edit.h"
20#include "chrome/browser/autocomplete/autocomplete_match.h"
21#include "chrome/browser/browser_process.h"
22#include "chrome/browser/debugger/devtools_manager.h"
23#include "chrome/browser/debugger/devtools_window.h"
24#include "chrome/browser/download/download_manager.h"
25#include "chrome/browser/download/download_util.h"
26#include "chrome/browser/download/save_package.h"
27#include "chrome/browser/extensions/extension_event_router.h"
28#include "chrome/browser/extensions/extension_service.h"
29#include "chrome/browser/metrics/user_metrics.h"
30#include "chrome/browser/net/browser_url_util.h"
31#include "chrome/browser/page_info_window.h"
32#include "chrome/browser/platform_util.h"
33#include "chrome/browser/prefs/pref_member.h"
34#include "chrome/browser/prefs/pref_service.h"
35#include "chrome/browser/printing/print_preview_tab_controller.h"
36#include "chrome/browser/profiles/profile.h"
37#include "chrome/browser/search_engines/template_url.h"
38#include "chrome/browser/search_engines/template_url_model.h"
39#include "chrome/browser/spellcheck_host.h"
40#include "chrome/browser/spellchecker_platform_engine.h"
41#include "chrome/browser/translate/translate_manager.h"
42#include "chrome/browser/translate/translate_prefs.h"
43#include "chrome/browser/translate/translate_tab_helper.h"
44#include "chrome/browser/ui/download/download_tab_helper.h"
45#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
46#include "chrome/common/chrome_constants.h"
47#include "chrome/common/chrome_switches.h"
48#include "chrome/common/content_restriction.h"
49#include "chrome/common/pref_names.h"
50#include "chrome/common/print_messages.h"
51#include "chrome/common/url_constants.h"
52#include "content/browser/child_process_security_policy.h"
53#include "content/browser/renderer_host/render_view_host.h"
54#include "content/browser/renderer_host/render_widget_host_view.h"
55#include "content/browser/tab_contents/navigation_entry.h"
56#include "content/browser/tab_contents/tab_contents.h"
57#include "grit/generated_resources.h"
58#include "net/base/escape.h"
59#include "net/url_request/url_request.h"
60#include "third_party/WebKit/Source/WebKit/chromium/public/WebContextMenuData.h"
61#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayerAction.h"
62#include "third_party/WebKit/Source/WebKit/chromium/public/WebTextDirection.h"
63#include "ui/base/l10n/l10n_util.h"
64#include "ui/gfx/favicon_size.h"
65#include "webkit/glue/webmenuitem.h"
66
67using WebKit::WebContextMenuData;
68using WebKit::WebMediaPlayerAction;
69
70namespace {
71
72bool IsCustomItemEnabled(const std::vector<WebMenuItem>& items, int id) {
73  DCHECK(id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
74         id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST);
75  for (size_t i = 0; i < items.size(); ++i) {
76    int action_id = IDC_CONTENT_CONTEXT_CUSTOM_FIRST + items[i].action;
77    if (action_id == id)
78      return items[i].enabled;
79    if (items[i].type == WebMenuItem::SUBMENU) {
80      if (IsCustomItemEnabled(items[i].submenu, id))
81        return true;
82    }
83  }
84  return false;
85}
86
87bool IsCustomItemChecked(const std::vector<WebMenuItem>& items, int id) {
88  DCHECK(id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
89         id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST);
90  for (size_t i = 0; i < items.size(); ++i) {
91    int action_id = IDC_CONTENT_CONTEXT_CUSTOM_FIRST + items[i].action;
92    if (action_id == id)
93      return items[i].checked;
94    if (items[i].type == WebMenuItem::SUBMENU) {
95      if (IsCustomItemChecked(items[i].submenu, id))
96        return true;
97    }
98  }
99  return false;
100}
101
102const size_t kMaxCustomMenuDepth = 5;
103const size_t kMaxCustomMenuTotalItems = 1000;
104
105void AddCustomItemsToMenu(const std::vector<WebMenuItem>& items,
106                          size_t depth,
107                          size_t* total_items,
108                          ui::SimpleMenuModel::Delegate* delegate,
109                          ui::SimpleMenuModel* menu_model) {
110  if (depth > kMaxCustomMenuDepth) {
111    LOG(ERROR) << "Custom menu too deeply nested.";
112    return;
113  }
114  for (size_t i = 0; i < items.size(); ++i) {
115    if (IDC_CONTENT_CONTEXT_CUSTOM_FIRST + items[i].action >=
116        IDC_CONTENT_CONTEXT_CUSTOM_LAST) {
117      LOG(ERROR) << "Custom menu action value too big.";
118      return;
119    }
120    if (*total_items >= kMaxCustomMenuTotalItems) {
121      LOG(ERROR) << "Custom menu too large (too many items).";
122      return;
123    }
124    (*total_items)++;
125    switch (items[i].type) {
126      case WebMenuItem::OPTION:
127        menu_model->AddItem(
128            items[i].action + IDC_CONTENT_CONTEXT_CUSTOM_FIRST,
129            items[i].label);
130        break;
131      case WebMenuItem::CHECKABLE_OPTION:
132        menu_model->AddCheckItem(
133            items[i].action + IDC_CONTENT_CONTEXT_CUSTOM_FIRST,
134            items[i].label);
135        break;
136      case WebMenuItem::GROUP:
137        // TODO(viettrungluu): I don't know what this is supposed to do.
138        NOTREACHED();
139        break;
140      case WebMenuItem::SEPARATOR:
141        menu_model->AddSeparator();
142        break;
143      case WebMenuItem::SUBMENU: {
144        ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate);
145        AddCustomItemsToMenu(items[i].submenu, depth + 1, total_items, delegate,
146                             submenu);
147        menu_model->AddSubMenu(
148            items[i].action + IDC_CONTENT_CONTEXT_CUSTOM_FIRST,
149            items[i].label,
150            submenu);
151        break;
152      }
153      default:
154        NOTREACHED();
155        break;
156    }
157  }
158}
159
160}  // namespace
161
162// static
163const size_t RenderViewContextMenu::kMaxExtensionItemTitleLength = 75;
164// static
165const size_t RenderViewContextMenu::kMaxSelectionTextLength = 50;
166
167// static
168bool RenderViewContextMenu::IsDevToolsURL(const GURL& url) {
169  return url.SchemeIs(chrome::kChromeDevToolsScheme) &&
170      url.host() == chrome::kChromeUIDevToolsHost;
171}
172
173// static
174bool RenderViewContextMenu::IsInternalResourcesURL(const GURL& url) {
175  if (!url.SchemeIs(chrome::kChromeUIScheme))
176    return false;
177  return url.host() == chrome::kChromeUISyncResourcesHost ||
178      url.host() == chrome::kChromeUIRemotingResourcesHost;
179}
180
181static const int kSpellcheckRadioGroup = 1;
182
183RenderViewContextMenu::RenderViewContextMenu(
184    TabContents* tab_contents,
185    const ContextMenuParams& params)
186    : params_(params),
187      source_tab_contents_(tab_contents),
188      profile_(tab_contents->profile()),
189      ALLOW_THIS_IN_INITIALIZER_LIST(menu_model_(this)),
190      external_(false),
191      ALLOW_THIS_IN_INITIALIZER_LIST(spellcheck_submenu_model_(this)),
192      ALLOW_THIS_IN_INITIALIZER_LIST(bidi_submenu_model_(this)) {
193}
194
195RenderViewContextMenu::~RenderViewContextMenu() {
196}
197
198// Menu construction functions -------------------------------------------------
199
200void RenderViewContextMenu::Init() {
201  InitMenu();
202  PlatformInit();
203}
204
205static bool ExtensionContextMatch(const ContextMenuParams& params,
206                                  ExtensionMenuItem::ContextList contexts) {
207  bool has_link = !params.link_url.is_empty();
208  bool has_selection = !params.selection_text.empty();
209  bool in_frame = !params.frame_url.is_empty();
210
211  if (contexts.Contains(ExtensionMenuItem::ALL) ||
212      (has_selection && contexts.Contains(ExtensionMenuItem::SELECTION)) ||
213      (has_link && contexts.Contains(ExtensionMenuItem::LINK)) ||
214      (params.is_editable && contexts.Contains(ExtensionMenuItem::EDITABLE)) ||
215      (in_frame && contexts.Contains(ExtensionMenuItem::FRAME))) {
216    return true;
217  }
218
219  switch (params.media_type) {
220    case WebContextMenuData::MediaTypeImage:
221      return contexts.Contains(ExtensionMenuItem::IMAGE);
222
223    case WebContextMenuData::MediaTypeVideo:
224      return contexts.Contains(ExtensionMenuItem::VIDEO);
225
226    case WebContextMenuData::MediaTypeAudio:
227      return contexts.Contains(ExtensionMenuItem::AUDIO);
228
229    default:
230      break;
231  }
232
233  // PAGE is the least specific context, so we only examine that if none of the
234  // other contexts apply (except for FRAME, which is included in PAGE for
235  // backwards compatibility).
236  if (!has_link && !has_selection && !params.is_editable &&
237      params.media_type == WebContextMenuData::MediaTypeNone &&
238      contexts.Contains(ExtensionMenuItem::PAGE))
239    return true;
240
241  return false;
242}
243
244static bool ExtensionPatternMatch(const ExtensionExtent& patterns,
245                                  const GURL& url) {
246  // No patterns means no restriction, so that implicitly matches.
247  if (patterns.is_empty())
248    return true;
249  return patterns.ContainsURL(url);
250}
251
252static const GURL& GetDocumentURL(const ContextMenuParams& params) {
253  return params.frame_url.is_empty() ? params.page_url : params.frame_url;
254}
255
256// Given a list of items, returns the ones that match given the contents
257// of |params| and the profile.
258static ExtensionMenuItem::List GetRelevantExtensionItems(
259    const ExtensionMenuItem::List& items,
260    const ContextMenuParams& params,
261    Profile* profile,
262    bool can_cross_incognito) {
263  ExtensionMenuItem::List result;
264  for (ExtensionMenuItem::List::const_iterator i = items.begin();
265       i != items.end(); ++i) {
266    const ExtensionMenuItem* item = *i;
267
268    if (!ExtensionContextMatch(params, item->contexts()))
269      continue;
270
271    const GURL& document_url = GetDocumentURL(params);
272    if (!ExtensionPatternMatch(item->document_url_patterns(), document_url))
273      continue;
274
275    const GURL& target_url =
276        params.src_url.is_empty() ? params.link_url : params.src_url;
277    if (!ExtensionPatternMatch(item->target_url_patterns(), target_url))
278      continue;
279
280    if (item->id().profile == profile || can_cross_incognito)
281      result.push_back(*i);
282  }
283  return result;
284}
285
286void RenderViewContextMenu::AppendExtensionItems(
287    const std::string& extension_id, int* index) {
288  ExtensionService* service = profile_->GetExtensionService();
289  ExtensionMenuManager* manager = service->menu_manager();
290  const Extension* extension = service->GetExtensionById(extension_id, false);
291  DCHECK_GE(*index, 0);
292  int max_index =
293      IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST - IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST;
294  if (!extension || *index >= max_index)
295    return;
296
297  // Find matching items.
298  const ExtensionMenuItem::List* all_items = manager->MenuItems(extension_id);
299  if (!all_items || all_items->empty())
300    return;
301  bool can_cross_incognito = service->CanCrossIncognito(extension);
302  ExtensionMenuItem::List items =
303      GetRelevantExtensionItems(*all_items, params_, profile_,
304                                can_cross_incognito);
305  if (items.empty())
306    return;
307
308  // If this is the first extension-provided menu item, add a separator.
309  if (*index == 0)
310    menu_model_.AddSeparator();
311
312  int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
313
314  // Extensions are only allowed one top-level slot (and it can't be a radio or
315  // checkbox item because we are going to put the extension icon next to it).
316  // If they have more than that, we automatically push them into a submenu.
317  string16 title;
318  ExtensionMenuItem::List submenu_items;
319  if (items.size() > 1 || items[0]->type() != ExtensionMenuItem::NORMAL) {
320    title = UTF8ToUTF16(extension->name());
321    submenu_items = items;
322  } else {
323    ExtensionMenuItem* item = items[0];
324    extension_item_map_[menu_id] = item->id();
325    title = item->TitleWithReplacement(PrintableSelectionText(),
326                                       kMaxExtensionItemTitleLength);
327    submenu_items = GetRelevantExtensionItems(item->children(), params_,
328                                              profile_, can_cross_incognito);
329  }
330
331  // Now add our item(s) to the menu_model_.
332  if (submenu_items.empty()) {
333    menu_model_.AddItem(menu_id, title);
334  } else {
335    ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(this);
336    extension_menu_models_.push_back(submenu);
337    menu_model_.AddSubMenu(menu_id, title, submenu);
338    RecursivelyAppendExtensionItems(submenu_items, can_cross_incognito, submenu,
339                                    index);
340  }
341  SetExtensionIcon(extension_id);
342}
343
344void RenderViewContextMenu::RecursivelyAppendExtensionItems(
345    const ExtensionMenuItem::List& items,
346    bool can_cross_incognito,
347    ui::SimpleMenuModel* menu_model,
348    int *index) {
349  string16 selection_text = PrintableSelectionText();
350  ExtensionMenuItem::Type last_type = ExtensionMenuItem::NORMAL;
351  int radio_group_id = 1;
352
353  for (ExtensionMenuItem::List::const_iterator i = items.begin();
354       i != items.end(); ++i) {
355    ExtensionMenuItem* item = *i;
356
357    // If last item was of type radio but the current one isn't, auto-insert
358    // a separator.  The converse case is handled below.
359    if (last_type == ExtensionMenuItem::RADIO &&
360        item->type() != ExtensionMenuItem::RADIO) {
361      menu_model->AddSeparator();
362      last_type = ExtensionMenuItem::SEPARATOR;
363    }
364
365    int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
366    if (menu_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
367      return;
368    extension_item_map_[menu_id] = item->id();
369    string16 title = item->TitleWithReplacement(selection_text,
370                                                kMaxExtensionItemTitleLength);
371    if (item->type() == ExtensionMenuItem::NORMAL) {
372      ExtensionMenuItem::List children =
373          GetRelevantExtensionItems(item->children(), params_,
374                                    profile_, can_cross_incognito);
375      if (children.empty()) {
376        menu_model->AddItem(menu_id, title);
377      } else {
378        ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(this);
379        extension_menu_models_.push_back(submenu);
380        menu_model->AddSubMenu(menu_id, title, submenu);
381        RecursivelyAppendExtensionItems(children, can_cross_incognito,
382                                        submenu, index);
383      }
384    } else if (item->type() == ExtensionMenuItem::CHECKBOX) {
385      menu_model->AddCheckItem(menu_id, title);
386    } else if (item->type() == ExtensionMenuItem::RADIO) {
387      if (i != items.begin() &&
388          last_type != ExtensionMenuItem::RADIO) {
389        radio_group_id++;
390
391        // Auto-append a separator if needed.
392        if (last_type != ExtensionMenuItem::SEPARATOR)
393          menu_model->AddSeparator();
394      }
395
396      menu_model->AddRadioItem(menu_id, title, radio_group_id);
397    } else if (item->type() == ExtensionMenuItem::SEPARATOR) {
398      if (i != items.begin() && last_type != ExtensionMenuItem::SEPARATOR) {
399        menu_model->AddSeparator();
400      }
401    }
402    last_type = item->type();
403  }
404}
405
406void RenderViewContextMenu::SetExtensionIcon(const std::string& extension_id) {
407  ExtensionService* service = profile_->GetExtensionService();
408  ExtensionMenuManager* menu_manager = service->menu_manager();
409
410  int index = menu_model_.GetItemCount() - 1;
411  DCHECK_GE(index, 0);
412
413  const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id);
414  DCHECK(icon.width() == kFaviconSize);
415  DCHECK(icon.height() == kFaviconSize);
416
417  menu_model_.SetIcon(index, icon);
418}
419
420void RenderViewContextMenu::AppendAllExtensionItems() {
421  extension_item_map_.clear();
422  ExtensionService* service = profile_->GetExtensionService();
423  if (!service)
424    return;  // In unit-tests, we may not have an ExtensionService.
425  ExtensionMenuManager* menu_manager = service->menu_manager();
426  const GURL& document_url = GetDocumentURL(params_);
427  if (!menu_manager->HasAllowedScheme(document_url))
428    return;
429
430  // Get a list of extension id's that have context menu items, and sort it by
431  // the extension's name.
432  std::set<std::string> ids = menu_manager->ExtensionIds();
433  std::vector<std::pair<std::string, std::string> > sorted_ids;
434  for (std::set<std::string>::iterator i = ids.begin(); i != ids.end(); ++i) {
435    const Extension* extension = service->GetExtensionById(*i, false);
436    if (extension)
437      sorted_ids.push_back(
438          std::pair<std::string, std::string>(extension->name(), *i));
439  }
440  // TODO(asargent) - See if this works properly for i18n names (bug 32363).
441  std::sort(sorted_ids.begin(), sorted_ids.end());
442
443  if (sorted_ids.empty())
444    return;
445
446  int index = 0;
447  base::TimeTicks begin = base::TimeTicks::Now();
448  std::vector<std::pair<std::string, std::string> >::const_iterator i;
449  for (i = sorted_ids.begin();
450       i != sorted_ids.end(); ++i) {
451    AppendExtensionItems(i->second, &index);
452  }
453  UMA_HISTOGRAM_TIMES("Extensions.ContextMenus_BuildTime",
454                      base::TimeTicks::Now() - begin);
455  UMA_HISTOGRAM_COUNTS("Extensions.ContextMenus_ItemCount", index);
456}
457
458void RenderViewContextMenu::InitMenu() {
459  bool has_link = !params_.link_url.is_empty();
460  bool has_selection = !params_.selection_text.empty();
461
462  if (AppendCustomItems()) {
463    // Don't add items for Pepper menu.
464    if (!params_.custom_context.is_pepper_menu)
465      AppendDeveloperItems();
466    return;
467  }
468
469  // When no special node or text is selected and selection has no link,
470  // show page items.
471  bool is_devtools = false;
472  if (params_.media_type == WebContextMenuData::MediaTypeNone &&
473      !has_link &&
474      !params_.is_editable &&
475      !has_selection) {
476    if (!params_.page_url.is_empty()) {
477      is_devtools = IsDevToolsURL(params_.page_url);
478      if (!is_devtools && !IsInternalResourcesURL(params_.page_url)) {
479        AppendPageItems();
480        // Merge in frame items if we clicked within a frame that needs them.
481        if (!params_.frame_url.is_empty()) {
482          is_devtools = IsDevToolsURL(params_.frame_url);
483          if (!is_devtools && !IsInternalResourcesURL(params_.frame_url)) {
484            menu_model_.AddSeparator();
485            AppendFrameItems();
486          }
487        }
488      }
489    } else {
490      DCHECK(params_.frame_url.is_empty());
491    }
492  }
493
494  if (has_link) {
495    AppendLinkItems();
496    if (params_.media_type != WebContextMenuData::MediaTypeNone)
497      menu_model_.AddSeparator();
498  }
499
500  switch (params_.media_type) {
501    case WebContextMenuData::MediaTypeNone:
502      break;
503    case WebContextMenuData::MediaTypeImage:
504      AppendImageItems();
505      break;
506    case WebContextMenuData::MediaTypeVideo:
507      AppendVideoItems();
508      break;
509    case WebContextMenuData::MediaTypeAudio:
510      AppendAudioItems();
511      break;
512    case WebContextMenuData::MediaTypePlugin:
513      AppendPluginItems();
514      break;
515#ifdef WEBCONTEXT_MEDIATYPEFILE_DEFINED
516    case WebContextMenuData::MediaTypeFile:
517      break;
518#endif
519  }
520
521  if (params_.is_editable)
522    AppendEditableItems();
523  else if (has_selection)
524    AppendCopyItem();
525
526  if (has_selection)
527    AppendSearchProvider();
528
529  if (!is_devtools)
530    AppendAllExtensionItems();
531
532  AppendDeveloperItems();
533}
534
535void RenderViewContextMenu::LookUpInDictionary() {
536  // Used only in the Mac port.
537  NOTREACHED();
538}
539
540bool RenderViewContextMenu::AppendCustomItems() {
541  size_t total_items = 0;
542  AddCustomItemsToMenu(params_.custom_items, 0, &total_items, this,
543                       &menu_model_);
544  return total_items > 0;
545}
546
547void RenderViewContextMenu::AppendDeveloperItems() {
548  // In the DevTools popup menu, "developer items" is normally the only
549  // section, so omit the separator there.
550  if (menu_model_.GetItemCount() > 0)
551    menu_model_.AddSeparator();
552  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_INSPECTELEMENT,
553                                  IDS_CONTENT_CONTEXT_INSPECTELEMENT);
554}
555
556void RenderViewContextMenu::AppendLinkItems() {
557  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB,
558                                  IDS_CONTENT_CONTEXT_OPENLINKNEWTAB);
559  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW,
560                                  IDS_CONTENT_CONTEXT_OPENLINKNEWWINDOW);
561  if (!external_) {
562    menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD,
563                                    IDS_CONTENT_CONTEXT_OPENLINKOFFTHERECORD);
564  }
565  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVELINKAS,
566                                  IDS_CONTENT_CONTEXT_SAVELINKAS);
567
568  menu_model_.AddItemWithStringId(
569      IDC_CONTENT_CONTEXT_COPYLINKLOCATION,
570      params_.link_url.SchemeIs(chrome::kMailToScheme) ?
571          IDS_CONTENT_CONTEXT_COPYEMAILADDRESS :
572          IDS_CONTENT_CONTEXT_COPYLINKLOCATION);
573}
574
575void RenderViewContextMenu::AppendImageItems() {
576  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVEIMAGEAS,
577                                  IDS_CONTENT_CONTEXT_SAVEIMAGEAS);
578  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYIMAGELOCATION,
579                                  IDS_CONTENT_CONTEXT_COPYIMAGELOCATION);
580  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYIMAGE,
581                                  IDS_CONTENT_CONTEXT_COPYIMAGE);
582  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB,
583                                  IDS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
584}
585
586void RenderViewContextMenu::AppendAudioItems() {
587  AppendMediaItems();
588  menu_model_.AddSeparator();
589  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVEAVAS,
590                                  IDS_CONTENT_CONTEXT_SAVEAUDIOAS);
591  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYAVLOCATION,
592                                  IDS_CONTENT_CONTEXT_COPYAUDIOLOCATION);
593  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENAVNEWTAB,
594                                  IDS_CONTENT_CONTEXT_OPENAUDIONEWTAB);
595}
596
597void RenderViewContextMenu::AppendVideoItems() {
598  AppendMediaItems();
599  menu_model_.AddSeparator();
600  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVEAVAS,
601                                  IDS_CONTENT_CONTEXT_SAVEVIDEOAS);
602  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYAVLOCATION,
603                                  IDS_CONTENT_CONTEXT_COPYVIDEOLOCATION);
604  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENAVNEWTAB,
605                                  IDS_CONTENT_CONTEXT_OPENVIDEONEWTAB);
606}
607
608void RenderViewContextMenu::AppendMediaItems() {
609  int media_flags = params_.media_flags;
610
611  menu_model_.AddItemWithStringId(
612      IDC_CONTENT_CONTEXT_PLAYPAUSE,
613      media_flags & WebContextMenuData::MediaPaused ?
614          IDS_CONTENT_CONTEXT_PLAY :
615          IDS_CONTENT_CONTEXT_PAUSE);
616
617  menu_model_.AddItemWithStringId(
618      IDC_CONTENT_CONTEXT_MUTE,
619      media_flags & WebContextMenuData::MediaMuted ?
620          IDS_CONTENT_CONTEXT_UNMUTE :
621          IDS_CONTENT_CONTEXT_MUTE);
622
623  menu_model_.AddCheckItemWithStringId(IDC_CONTENT_CONTEXT_LOOP,
624                                       IDS_CONTENT_CONTEXT_LOOP);
625  menu_model_.AddCheckItemWithStringId(IDC_CONTENT_CONTEXT_CONTROLS,
626                                       IDS_CONTENT_CONTEXT_CONTROLS);
627}
628
629void RenderViewContextMenu::AppendPluginItems() {
630  if (params_.page_url == params_.src_url) {
631    // Full page plugin, so show page menu items.
632    if (params_.link_url.is_empty() && params_.selection_text.empty())
633      AppendPageItems();
634  } else {
635    menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SAVEAVAS,
636                                    IDS_CONTENT_CONTEXT_SAVEPAGEAS);
637    menu_model_.AddItemWithStringId(IDC_PRINT, IDS_CONTENT_CONTEXT_PRINT);
638  }
639}
640
641void RenderViewContextMenu::AppendPageItems() {
642  menu_model_.AddItemWithStringId(IDC_BACK, IDS_CONTENT_CONTEXT_BACK);
643  menu_model_.AddItemWithStringId(IDC_FORWARD, IDS_CONTENT_CONTEXT_FORWARD);
644  menu_model_.AddItemWithStringId(IDC_RELOAD, IDS_CONTENT_CONTEXT_RELOAD);
645  menu_model_.AddSeparator();
646  menu_model_.AddItemWithStringId(IDC_SAVE_PAGE,
647                                  IDS_CONTENT_CONTEXT_SAVEPAGEAS);
648  menu_model_.AddItemWithStringId(IDC_PRINT, IDS_CONTENT_CONTEXT_PRINT);
649
650  std::string locale = g_browser_process->GetApplicationLocale();
651  locale = TranslateManager::GetLanguageCode(locale);
652  string16 language = l10n_util::GetDisplayNameForLocale(locale, locale, true);
653  menu_model_.AddItem(
654      IDC_CONTENT_CONTEXT_TRANSLATE,
655      l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_TRANSLATE, language));
656
657  menu_model_.AddItemWithStringId(IDC_VIEW_SOURCE,
658                                  IDS_CONTENT_CONTEXT_VIEWPAGESOURCE);
659  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_VIEWPAGEINFO,
660                                  IDS_CONTENT_CONTEXT_VIEWPAGEINFO);
661}
662
663void RenderViewContextMenu::AppendFrameItems() {
664  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_RELOADFRAME,
665                                  IDS_CONTENT_CONTEXT_RELOADFRAME);
666  // These two menu items have yet to be implemented.
667  // http://code.google.com/p/chromium/issues/detail?id=11827
668  //   IDS_CONTENT_CONTEXT_SAVEFRAMEAS
669  //   IDS_CONTENT_CONTEXT_PRINTFRAME
670  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE,
671                                  IDS_CONTENT_CONTEXT_VIEWFRAMESOURCE);
672  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_VIEWFRAMEINFO,
673                                  IDS_CONTENT_CONTEXT_VIEWFRAMEINFO);
674}
675
676void RenderViewContextMenu::AppendCopyItem() {
677  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPY,
678                                  IDS_CONTENT_CONTEXT_COPY);
679}
680
681void RenderViewContextMenu::AppendSearchProvider() {
682  DCHECK(profile_);
683
684  TrimWhitespace(params_.selection_text, TRIM_ALL, &params_.selection_text);
685  if (params_.selection_text.empty())
686    return;
687
688  AutocompleteMatch match;
689  profile_->GetAutocompleteClassifier()->Classify(
690      params_.selection_text, string16(), false, &match, NULL);
691  selection_navigation_url_ = match.destination_url;
692  if (!selection_navigation_url_.is_valid())
693    return;
694
695  string16 printable_selection_text = PrintableSelectionText();
696  // Escape "&" as "&&".
697  for (size_t i = printable_selection_text.find('&'); i != string16::npos;
698       i = printable_selection_text.find('&', i + 2))
699    printable_selection_text.insert(i, 1, '&');
700
701  if (match.transition == PageTransition::TYPED) {
702    if (ChildProcessSecurityPolicy::GetInstance()->IsWebSafeScheme(
703        selection_navigation_url_.scheme())) {
704      menu_model_.AddItem(
705          IDC_CONTENT_CONTEXT_GOTOURL,
706          l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_GOTOURL,
707                                     printable_selection_text));
708    }
709  } else {
710    const TemplateURL* const default_provider =
711        profile_->GetTemplateURLModel()->GetDefaultSearchProvider();
712    if (!default_provider)
713      return;
714    menu_model_.AddItem(
715        IDC_CONTENT_CONTEXT_SEARCHWEBFOR,
716        l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_SEARCHWEBFOR,
717                                   default_provider->short_name(),
718                                   printable_selection_text));
719  }
720}
721
722void RenderViewContextMenu::AppendEditableItems() {
723  // Append Dictionary spell check suggestions.
724  for (size_t i = 0; i < params_.dictionary_suggestions.size() &&
725       IDC_SPELLCHECK_SUGGESTION_0 + i <= IDC_SPELLCHECK_SUGGESTION_LAST;
726       ++i) {
727    menu_model_.AddItem(IDC_SPELLCHECK_SUGGESTION_0 + static_cast<int>(i),
728                        params_.dictionary_suggestions[i]);
729  }
730  if (!params_.dictionary_suggestions.empty())
731    menu_model_.AddSeparator();
732
733  // If word is misspelled, give option for "Add to dictionary"
734  if (!params_.misspelled_word.empty()) {
735    if (params_.dictionary_suggestions.empty()) {
736      menu_model_.AddItem(0,
737          l10n_util::GetStringUTF16(
738              IDS_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS));
739    }
740    menu_model_.AddItemWithStringId(IDC_SPELLCHECK_ADD_TO_DICTIONARY,
741                                    IDS_CONTENT_CONTEXT_ADD_TO_DICTIONARY);
742    menu_model_.AddSeparator();
743  }
744
745  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_UNDO,
746                                  IDS_CONTENT_CONTEXT_UNDO);
747  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_REDO,
748                                  IDS_CONTENT_CONTEXT_REDO);
749  menu_model_.AddSeparator();
750  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_CUT,
751                                  IDS_CONTENT_CONTEXT_CUT);
752  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPY,
753                                  IDS_CONTENT_CONTEXT_COPY);
754  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_PASTE,
755                                  IDS_CONTENT_CONTEXT_PASTE);
756  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_DELETE,
757                                  IDS_CONTENT_CONTEXT_DELETE);
758  menu_model_.AddSeparator();
759
760  AppendSpellcheckOptionsSubMenu();
761
762#if defined(OS_MACOSX)
763  // OS X provides a contextual menu to set writing direction for BiDi
764  // languages.
765  // This functionality is exposed as a keyboard shortcut on Windows & Linux.
766  AppendBidiSubMenu();
767#endif  // OS_MACOSX
768
769  menu_model_.AddSeparator();
770  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_SELECTALL,
771                                  IDS_CONTENT_CONTEXT_SELECTALL);
772}
773
774void RenderViewContextMenu::AppendSpellcheckOptionsSubMenu() {
775  // Add Spell Check languages to sub menu.
776  std::vector<std::string> spellcheck_languages;
777  SpellCheckHost::GetSpellCheckLanguages(profile_,
778      &spellcheck_languages);
779  DCHECK(spellcheck_languages.size() <
780         IDC_SPELLCHECK_LANGUAGES_LAST - IDC_SPELLCHECK_LANGUAGES_FIRST);
781  const std::string app_locale = g_browser_process->GetApplicationLocale();
782  for (size_t i = 0; i < spellcheck_languages.size(); ++i) {
783    string16 display_name(l10n_util::GetDisplayNameForLocale(
784        spellcheck_languages[i], app_locale, true));
785    spellcheck_submenu_model_.AddRadioItem(
786        IDC_SPELLCHECK_LANGUAGES_FIRST + i,
787        display_name,
788        kSpellcheckRadioGroup);
789  }
790
791  // Add item in the sub menu to pop up the fonts and languages options menu.
792  spellcheck_submenu_model_.AddSeparator();
793  spellcheck_submenu_model_.AddItemWithStringId(
794      IDC_CONTENT_CONTEXT_LANGUAGE_SETTINGS,
795      IDS_CONTENT_CONTEXT_LANGUAGE_SETTINGS);
796
797  // Add 'Check the spelling of this field' item in the sub menu.
798  spellcheck_submenu_model_.AddCheckItem(
799      IDC_CHECK_SPELLING_OF_THIS_FIELD,
800      l10n_util::GetStringUTF16(
801          IDS_CONTENT_CONTEXT_CHECK_SPELLING_OF_THIS_FIELD));
802
803  // Add option for showing the spelling panel if the platform spellchecker
804  // supports it.
805  if (SpellCheckerPlatform::SpellCheckerAvailable() &&
806      SpellCheckerPlatform::SpellCheckerProvidesPanel()) {
807    spellcheck_submenu_model_.AddCheckItem(
808        IDC_SPELLPANEL_TOGGLE,
809        l10n_util::GetStringUTF16(
810            SpellCheckerPlatform::SpellingPanelVisible() ?
811                IDS_CONTENT_CONTEXT_HIDE_SPELLING_PANEL :
812                IDS_CONTENT_CONTEXT_SHOW_SPELLING_PANEL));
813  }
814
815  menu_model_.AddSubMenu(
816      IDC_SPELLCHECK_MENU,
817      l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_SPELLCHECK_MENU),
818      &spellcheck_submenu_model_);
819}
820
821#if defined(OS_MACOSX)
822void RenderViewContextMenu::AppendBidiSubMenu() {
823  bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_DEFAULT,
824      l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_DEFAULT));
825  bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_LTR,
826      l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_LTR));
827  bidi_submenu_model_.AddCheckItem(IDC_WRITING_DIRECTION_RTL,
828      l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_RTL));
829
830  menu_model_.AddSubMenu(
831      IDC_WRITING_DIRECTION_MENU,
832      l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_WRITING_DIRECTION_MENU),
833      &bidi_submenu_model_);
834}
835#endif  // OS_MACOSX
836
837ExtensionMenuItem* RenderViewContextMenu::GetExtensionMenuItem(int id) const {
838  ExtensionMenuManager* manager =
839      profile_->GetExtensionService()->menu_manager();
840  std::map<int, ExtensionMenuItem::Id>::const_iterator i =
841      extension_item_map_.find(id);
842  if (i != extension_item_map_.end()) {
843    ExtensionMenuItem* item = manager->GetItemById(i->second);
844    if (item)
845      return item;
846  }
847  return NULL;
848}
849
850// Menu delegate functions -----------------------------------------------------
851
852bool RenderViewContextMenu::IsCommandIdEnabled(int id) const {
853  if (id == IDC_PRINT &&
854      (source_tab_contents_->content_restrictions() &
855          CONTENT_RESTRICTION_PRINT)) {
856    return false;
857  }
858
859  if (id == IDC_SAVE_PAGE &&
860      (source_tab_contents_->content_restrictions() &
861          CONTENT_RESTRICTION_SAVE)) {
862    return false;
863  }
864
865  // Allow Spell Check language items on sub menu for text area context menu.
866  if ((id >= IDC_SPELLCHECK_LANGUAGES_FIRST) &&
867      (id < IDC_SPELLCHECK_LANGUAGES_LAST)) {
868    return profile_->GetPrefs()->GetBoolean(prefs::kEnableSpellCheck);
869  }
870
871  // Custom items.
872  if (id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
873      id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST) {
874    return IsCustomItemEnabled(params_.custom_items, id);
875  }
876
877  // Extension items.
878  if (id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
879      id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
880    // In the future we may add APIs for extensions to disable items, but for
881    // now all items are implicitly enabled.
882    return true;
883  }
884
885  switch (id) {
886    case IDC_BACK:
887      return source_tab_contents_->controller().CanGoBack();
888
889    case IDC_FORWARD:
890      return source_tab_contents_->controller().CanGoForward();
891
892    case IDC_RELOAD:
893      return source_tab_contents_->delegate() &&
894          source_tab_contents_->delegate()->CanReloadContents(
895              source_tab_contents_);
896
897    case IDC_VIEW_SOURCE:
898    case IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE:
899      return source_tab_contents_->controller().CanViewSource();
900
901    case IDC_CONTENT_CONTEXT_INSPECTELEMENT:
902    // Viewing page info is not a developer command but is meaningful for the
903    // same set of pages which developer commands are meaningful for.
904    case IDC_CONTENT_CONTEXT_VIEWPAGEINFO:
905      return IsDevCommandEnabled(id);
906
907    case IDC_CONTENT_CONTEXT_TRANSLATE: {
908      TranslateTabHelper* helper =
909          TabContentsWrapper::GetCurrentWrapperForContents(
910              source_tab_contents_)->translate_tab_helper();
911      std::string original_lang =
912          helper->language_state().original_language();
913      std::string target_lang = g_browser_process->GetApplicationLocale();
914      target_lang = TranslateManager::GetLanguageCode(target_lang);
915      // Note that we intentionally enable the menu even if the original and
916      // target languages are identical.  This is to give a way to user to
917      // translate a page that might contains text fragments in a different
918      // language.
919      return !!(params_.edit_flags & WebContextMenuData::CanTranslate) &&
920             helper->language_state().page_translatable() &&
921             !original_lang.empty() &&  // Did we receive the page language yet?
922             // Only allow translating languages we explitly support and the
923             // unknown language (in which case the page language is detected on
924             // the server side).
925             (original_lang == chrome::kUnknownLanguageCode ||
926                 TranslateManager::IsSupportedLanguage(original_lang)) &&
927             !helper->language_state().IsPageTranslated() &&
928             !source_tab_contents_->interstitial_page() &&
929             TranslateManager::IsTranslatableURL(params_.page_url);
930    }
931
932    case IDC_CONTENT_CONTEXT_OPENLINKNEWTAB:
933    case IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW:
934      return params_.link_url.is_valid();
935
936    case IDC_CONTENT_CONTEXT_COPYLINKLOCATION:
937      return params_.unfiltered_link_url.is_valid();
938
939    case IDC_CONTENT_CONTEXT_SAVELINKAS:
940      return params_.link_url.is_valid() &&
941             net::URLRequest::IsHandledURL(params_.link_url);
942
943    case IDC_CONTENT_CONTEXT_SAVEIMAGEAS:
944      return params_.src_url.is_valid() &&
945             net::URLRequest::IsHandledURL(params_.src_url);
946
947    case IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB:
948      // The images shown in the most visited thumbnails do not currently open
949      // in a new tab as they should. Disabling this context menu option for
950      // now, as a quick hack, before we resolve this issue (Issue = 2608).
951      // TODO(sidchat): Enable this option once this issue is resolved.
952      if (params_.src_url.scheme() == chrome::kChromeUIScheme ||
953          !params_.src_url.is_valid())
954        return false;
955      return true;
956
957    case IDC_CONTENT_CONTEXT_COPYIMAGE:
958      return !params_.is_image_blocked;
959
960    // Media control commands should all be disabled if the player is in an
961    // error state.
962    case IDC_CONTENT_CONTEXT_PLAYPAUSE:
963    case IDC_CONTENT_CONTEXT_LOOP:
964      return (params_.media_flags &
965              WebContextMenuData::MediaInError) == 0;
966
967    // Mute and unmute should also be disabled if the player has no audio.
968    case IDC_CONTENT_CONTEXT_MUTE:
969      return (params_.media_flags &
970              WebContextMenuData::MediaHasAudio) != 0 &&
971             (params_.media_flags &
972              WebContextMenuData::MediaInError) == 0;
973
974    // Media controls can be toggled only for video player. If we toggle
975    // controls for audio then the player disappears, and there is no way to
976    // return it back.
977    case IDC_CONTENT_CONTEXT_CONTROLS:
978      return (params_.media_flags &
979              WebContextMenuData::MediaHasVideo) != 0;
980
981    case IDC_CONTENT_CONTEXT_COPYAVLOCATION:
982    case IDC_CONTENT_CONTEXT_COPYIMAGELOCATION:
983      return params_.src_url.is_valid();
984
985    case IDC_CONTENT_CONTEXT_SAVEAVAS:
986      return (params_.media_flags &
987              WebContextMenuData::MediaCanSave) &&
988             params_.src_url.is_valid() &&
989             net::URLRequest::IsHandledURL(params_.src_url);
990
991    case IDC_CONTENT_CONTEXT_OPENAVNEWTAB:
992      return true;
993
994    case IDC_SAVE_PAGE: {
995      // Instead of using GetURL here, we use url() (which is the "real" url of
996      // the page) from the NavigationEntry because its reflects their origin
997      // rather than the display one (returned by GetURL) which may be
998      // different (like having "view-source:" on the front).
999      NavigationEntry* active_entry =
1000          source_tab_contents_->controller().GetActiveEntry();
1001      return SavePackage::IsSavableURL(
1002          (active_entry) ? active_entry->url() : GURL());
1003    }
1004
1005    case IDC_CONTENT_CONTEXT_RELOADFRAME:
1006      return params_.frame_url.is_valid();
1007
1008    case IDC_CONTENT_CONTEXT_UNDO:
1009      return !!(params_.edit_flags & WebContextMenuData::CanUndo);
1010
1011    case IDC_CONTENT_CONTEXT_REDO:
1012      return !!(params_.edit_flags & WebContextMenuData::CanRedo);
1013
1014    case IDC_CONTENT_CONTEXT_CUT:
1015      return !!(params_.edit_flags & WebContextMenuData::CanCut);
1016
1017    case IDC_CONTENT_CONTEXT_COPY:
1018      return !!(params_.edit_flags & WebContextMenuData::CanCopy);
1019
1020    case IDC_CONTENT_CONTEXT_PASTE:
1021      return !!(params_.edit_flags & WebContextMenuData::CanPaste);
1022
1023    case IDC_CONTENT_CONTEXT_DELETE:
1024      return !!(params_.edit_flags & WebContextMenuData::CanDelete);
1025
1026    case IDC_CONTENT_CONTEXT_SELECTALL:
1027      return !!(params_.edit_flags & WebContextMenuData::CanSelectAll);
1028
1029    case IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD:
1030      return !profile_->IsOffTheRecord() && params_.link_url.is_valid() &&
1031             profile_->GetPrefs()->GetBoolean(prefs::kIncognitoEnabled);
1032
1033    case IDC_SPELLCHECK_ADD_TO_DICTIONARY:
1034      return !params_.misspelled_word.empty();
1035
1036    case IDC_PRINT:
1037      if (g_browser_process->local_state() &&
1038          !g_browser_process->local_state()->GetBoolean(
1039              prefs::kPrintingEnabled)) {
1040        return false;
1041      }
1042      return params_.media_type == WebContextMenuData::MediaTypeNone ||
1043             params_.media_flags & WebContextMenuData::MediaCanPrint;
1044
1045    case IDC_CONTENT_CONTEXT_SEARCHWEBFOR:
1046    case IDC_CONTENT_CONTEXT_GOTOURL:
1047    case IDC_SPELLCHECK_SUGGESTION_0:
1048    case IDC_SPELLCHECK_SUGGESTION_1:
1049    case IDC_SPELLCHECK_SUGGESTION_2:
1050    case IDC_SPELLCHECK_SUGGESTION_3:
1051    case IDC_SPELLCHECK_SUGGESTION_4:
1052    case IDC_SPELLPANEL_TOGGLE:
1053#if !defined(OS_MACOSX)
1054    // TODO(jeremy): re-enable - http://crbug.com/34512 .
1055    case IDC_CONTENT_CONTEXT_LANGUAGE_SETTINGS:
1056#endif
1057    case IDC_CONTENT_CONTEXT_VIEWFRAMEINFO:
1058      return true;
1059
1060    case IDC_CHECK_SPELLING_OF_THIS_FIELD:
1061      return profile_->GetPrefs()->GetBoolean(prefs::kEnableSpellCheck);
1062
1063#if defined(OS_MACOSX)
1064    // TODO(jeremy): re-enable - http://crbug.com/34512 .
1065    case IDC_CONTENT_CONTEXT_LANGUAGE_SETTINGS:
1066      return false;
1067#endif
1068
1069#if defined(OS_MACOSX)
1070    case IDC_WRITING_DIRECTION_DEFAULT:  // Provided to match OS defaults.
1071      return params_.writing_direction_default &
1072          WebContextMenuData::CheckableMenuItemEnabled;
1073    case IDC_WRITING_DIRECTION_RTL:
1074      return params_.writing_direction_right_to_left &
1075          WebContextMenuData::CheckableMenuItemEnabled;
1076    case IDC_WRITING_DIRECTION_LTR:
1077      return params_.writing_direction_left_to_right &
1078          WebContextMenuData::CheckableMenuItemEnabled;
1079    case IDC_WRITING_DIRECTION_MENU:
1080      return true;
1081    case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY:
1082      // This is OK because the menu is not shown when it isn't
1083      // appropriate.
1084      return true;
1085#elif defined(OS_POSIX)
1086    // TODO(suzhe): this should not be enabled for password fields.
1087    case IDC_INPUT_METHODS_MENU:
1088      return true;
1089#endif
1090
1091    case IDC_SPELLCHECK_MENU:
1092      return true;
1093
1094    default:
1095      NOTREACHED();
1096      return false;
1097  }
1098}
1099
1100bool RenderViewContextMenu::IsCommandIdChecked(int id) const {
1101  // See if the video is set to looping.
1102  if (id == IDC_CONTENT_CONTEXT_LOOP) {
1103    return (params_.media_flags &
1104            WebContextMenuData::MediaLoop) != 0;
1105  }
1106
1107  if (id == IDC_CONTENT_CONTEXT_CONTROLS) {
1108    return (params_.media_flags &
1109            WebContextMenuData::MediaControlRootElement) != 0;
1110  }
1111
1112  // Custom items.
1113  if (id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
1114      id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST) {
1115    return IsCustomItemChecked(params_.custom_items, id);
1116  }
1117
1118  // Extension items.
1119  if (id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
1120      id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
1121    ExtensionMenuItem* item = GetExtensionMenuItem(id);
1122    if (item)
1123      return item->checked();
1124    else
1125      return false;
1126  }
1127
1128#if defined(OS_MACOSX)
1129    if (id == IDC_WRITING_DIRECTION_DEFAULT)
1130      return params_.writing_direction_default &
1131          WebContextMenuData::CheckableMenuItemChecked;
1132    if (id == IDC_WRITING_DIRECTION_RTL)
1133      return params_.writing_direction_right_to_left &
1134          WebContextMenuData::CheckableMenuItemChecked;
1135    if (id == IDC_WRITING_DIRECTION_LTR)
1136      return params_.writing_direction_left_to_right &
1137          WebContextMenuData::CheckableMenuItemChecked;
1138    if (id == IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY)
1139      return false;
1140#endif  // OS_MACOSX
1141
1142  // Check box for 'Check the Spelling of this field'.
1143  if (id == IDC_CHECK_SPELLING_OF_THIS_FIELD) {
1144    return (params_.spellcheck_enabled &&
1145            profile_->GetPrefs()->GetBoolean(prefs::kEnableSpellCheck));
1146  }
1147
1148  // Don't bother getting the display language vector if this isn't a spellcheck
1149  // language.
1150  if ((id < IDC_SPELLCHECK_LANGUAGES_FIRST) ||
1151      (id >= IDC_SPELLCHECK_LANGUAGES_LAST))
1152    return false;
1153
1154  std::vector<std::string> languages;
1155  return SpellCheckHost::GetSpellCheckLanguages(profile_, &languages) ==
1156      (id - IDC_SPELLCHECK_LANGUAGES_FIRST);
1157}
1158
1159void RenderViewContextMenu::ExecuteCommand(int id) {
1160  // Check to see if one of the spell check language ids have been clicked.
1161  if (id >= IDC_SPELLCHECK_LANGUAGES_FIRST &&
1162      id < IDC_SPELLCHECK_LANGUAGES_LAST) {
1163    const size_t language_number = id - IDC_SPELLCHECK_LANGUAGES_FIRST;
1164    std::vector<std::string> languages;
1165    SpellCheckHost::GetSpellCheckLanguages(profile_, &languages);
1166    if (language_number < languages.size()) {
1167      StringPrefMember dictionary_language;
1168      dictionary_language.Init(prefs::kSpellCheckDictionary,
1169          profile_->GetPrefs(), NULL);
1170      dictionary_language.SetValue(languages[language_number]);
1171    }
1172    return;
1173  }
1174
1175  // Process custom actions range.
1176  if (id >= IDC_CONTENT_CONTEXT_CUSTOM_FIRST &&
1177      id <= IDC_CONTENT_CONTEXT_CUSTOM_LAST) {
1178    unsigned action = id - IDC_CONTENT_CONTEXT_CUSTOM_FIRST;
1179    source_tab_contents_->render_view_host()->PerformCustomContextMenuAction(
1180        params_.custom_context, action);
1181    return;
1182  }
1183
1184  // Process extension menu items.
1185  if (id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
1186      id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
1187    ExtensionMenuManager* manager =
1188        profile_->GetExtensionService()->menu_manager();
1189    std::map<int, ExtensionMenuItem::Id>::const_iterator i =
1190        extension_item_map_.find(id);
1191    if (i != extension_item_map_.end()) {
1192      manager->ExecuteCommand(profile_, source_tab_contents_, params_,
1193                              i->second);
1194    }
1195    return;
1196  }
1197
1198
1199  switch (id) {
1200    case IDC_CONTENT_CONTEXT_OPENLINKNEWTAB:
1201      OpenURL(params_.link_url,
1202              source_tab_contents_->delegate() &&
1203              source_tab_contents_->delegate()->IsApplication() ?
1204                  NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB,
1205              PageTransition::LINK);
1206      break;
1207
1208    case IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW:
1209      OpenURL(params_.link_url, NEW_WINDOW, PageTransition::LINK);
1210      break;
1211
1212    case IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD:
1213      OpenURL(params_.link_url, OFF_THE_RECORD, PageTransition::LINK);
1214      break;
1215
1216    case IDC_CONTENT_CONTEXT_SAVEAVAS:
1217    case IDC_CONTENT_CONTEXT_SAVEIMAGEAS:
1218    case IDC_CONTENT_CONTEXT_SAVELINKAS: {
1219      download_util::RecordDownloadCount(
1220          download_util::INITIATED_BY_CONTEXT_MENU_COUNT);
1221      const GURL& referrer =
1222          params_.frame_url.is_empty() ? params_.page_url : params_.frame_url;
1223      const GURL& url =
1224          (id == IDC_CONTENT_CONTEXT_SAVELINKAS ? params_.link_url :
1225                                                  params_.src_url);
1226      DownloadManager* dlm = profile_->GetDownloadManager();
1227      dlm->DownloadUrl(url, referrer, params_.frame_charset,
1228                       source_tab_contents_);
1229      break;
1230    }
1231
1232    case IDC_CONTENT_CONTEXT_COPYLINKLOCATION:
1233      WriteURLToClipboard(params_.unfiltered_link_url);
1234      break;
1235
1236    case IDC_CONTENT_CONTEXT_COPYIMAGELOCATION:
1237    case IDC_CONTENT_CONTEXT_COPYAVLOCATION:
1238      WriteURLToClipboard(params_.src_url);
1239      break;
1240
1241    case IDC_CONTENT_CONTEXT_COPYIMAGE:
1242      CopyImageAt(params_.x, params_.y);
1243      break;
1244
1245    case IDC_CONTENT_CONTEXT_OPENIMAGENEWTAB:
1246    case IDC_CONTENT_CONTEXT_OPENAVNEWTAB:
1247      OpenURL(params_.src_url, NEW_BACKGROUND_TAB, PageTransition::LINK);
1248      break;
1249
1250    case IDC_CONTENT_CONTEXT_PLAYPAUSE: {
1251      bool play = !!(params_.media_flags & WebContextMenuData::MediaPaused);
1252      if (play) {
1253        UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Play"),
1254                                  profile_);
1255      } else {
1256        UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Pause"),
1257                                  profile_);
1258      }
1259      MediaPlayerActionAt(gfx::Point(params_.x, params_.y),
1260                          WebMediaPlayerAction(
1261                              WebMediaPlayerAction::Play, play));
1262      break;
1263    }
1264
1265    case IDC_CONTENT_CONTEXT_MUTE: {
1266      bool mute = !(params_.media_flags & WebContextMenuData::MediaMuted);
1267      if (mute) {
1268        UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Mute"),
1269                                  profile_);
1270      } else {
1271        UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Unmute"),
1272                                  profile_);
1273      }
1274      MediaPlayerActionAt(gfx::Point(params_.x, params_.y),
1275                          WebMediaPlayerAction(
1276                              WebMediaPlayerAction::Mute, mute));
1277      break;
1278    }
1279
1280    case IDC_CONTENT_CONTEXT_LOOP:
1281      UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Loop"),
1282                                profile_);
1283      MediaPlayerActionAt(gfx::Point(params_.x, params_.y),
1284                          WebMediaPlayerAction(
1285                              WebMediaPlayerAction::Loop,
1286                              !IsCommandIdChecked(IDC_CONTENT_CONTEXT_LOOP)));
1287      break;
1288
1289    case IDC_CONTENT_CONTEXT_CONTROLS:
1290      UserMetrics::RecordAction(UserMetricsAction("MediaContextMenu_Controls"),
1291                                profile_);
1292      MediaPlayerActionAt(
1293          gfx::Point(params_.x, params_.y),
1294          WebMediaPlayerAction(
1295              WebMediaPlayerAction::Controls,
1296              !IsCommandIdChecked(IDC_CONTENT_CONTEXT_CONTROLS)));
1297      break;
1298
1299    case IDC_BACK:
1300      source_tab_contents_->controller().GoBack();
1301      break;
1302
1303    case IDC_FORWARD:
1304      source_tab_contents_->controller().GoForward();
1305      break;
1306
1307    case IDC_SAVE_PAGE: {
1308      TabContentsWrapper* wrapper =
1309          TabContentsWrapper::GetCurrentWrapperForContents(
1310              source_tab_contents_);
1311      wrapper->download_tab_helper()->OnSavePage();
1312      break;
1313    }
1314
1315    case IDC_RELOAD:
1316      // Prevent the modal "Resubmit form post" dialog from appearing in the
1317      // context of an external context menu.
1318      source_tab_contents_->controller().Reload(!external_);
1319      break;
1320
1321    case IDC_PRINT:
1322      if (params_.media_type == WebContextMenuData::MediaTypeNone) {
1323        if (CommandLine::ForCurrentProcess()->HasSwitch(
1324            switches::kEnablePrintPreview)) {
1325          printing::PrintPreviewTabController::PrintPreview(
1326              source_tab_contents_);
1327        } else {
1328          TabContentsWrapper* wrapper =
1329              TabContentsWrapper::GetCurrentWrapperForContents(
1330                  source_tab_contents_);
1331          wrapper->print_view_manager()->PrintNow();
1332        }
1333      } else {
1334        RenderViewHost* rvh = source_tab_contents_->render_view_host();
1335        rvh->Send(new PrintMsg_PrintNodeUnderContextMenu(rvh->routing_id()));
1336      }
1337      break;
1338
1339    case IDC_VIEW_SOURCE:
1340      source_tab_contents_->ViewSource();
1341      break;
1342
1343    case IDC_CONTENT_CONTEXT_INSPECTELEMENT:
1344      Inspect(params_.x, params_.y);
1345      break;
1346
1347    case IDC_CONTENT_CONTEXT_VIEWPAGEINFO: {
1348      NavigationEntry* nav_entry =
1349          source_tab_contents_->controller().GetActiveEntry();
1350      source_tab_contents_->ShowPageInfo(nav_entry->url(), nav_entry->ssl(),
1351                                         true);
1352      break;
1353    }
1354
1355    case IDC_CONTENT_CONTEXT_TRANSLATE: {
1356      // A translation might have been triggered by the time the menu got
1357      // selected, do nothing in that case.
1358      TranslateTabHelper* helper =
1359          TabContentsWrapper::GetCurrentWrapperForContents(
1360              source_tab_contents_)->translate_tab_helper();
1361      if (helper->language_state().IsPageTranslated() ||
1362          helper->language_state().translation_pending()) {
1363        return;
1364      }
1365      std::string original_lang = helper->language_state().original_language();
1366      std::string target_lang = g_browser_process->GetApplicationLocale();
1367      target_lang = TranslateManager::GetLanguageCode(target_lang);
1368      // Since the user decided to translate for that language and site, clears
1369      // any preferences for not translating them.
1370      TranslatePrefs prefs(profile_->GetPrefs());
1371      prefs.RemoveLanguageFromBlacklist(original_lang);
1372      prefs.RemoveSiteFromBlacklist(params_.page_url.HostNoBrackets());
1373      TranslateManager::GetInstance()->TranslatePage(
1374          source_tab_contents_, original_lang, target_lang);
1375      break;
1376    }
1377
1378    case IDC_CONTENT_CONTEXT_RELOADFRAME:
1379      source_tab_contents_->render_view_host()->ReloadFrame();
1380      break;
1381
1382    case IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE:
1383      source_tab_contents_->ViewFrameSource(params_.frame_url,
1384                                            params_.frame_content_state);
1385      break;
1386
1387    case IDC_CONTENT_CONTEXT_VIEWFRAMEINFO: {
1388      // Deserialize the SSL info.
1389      NavigationEntry::SSLStatus ssl;
1390      if (!params_.security_info.empty()) {
1391        int cert_id, cert_status, security_bits, connection_status;
1392        SSLManager::DeserializeSecurityInfo(params_.security_info,
1393                                            &cert_id,
1394                                            &cert_status,
1395                                            &security_bits,
1396                                            &connection_status);
1397        ssl.set_cert_id(cert_id);
1398        ssl.set_cert_status(cert_status);
1399        ssl.set_security_bits(security_bits);
1400        ssl.set_connection_status(connection_status);
1401      }
1402      source_tab_contents_->ShowPageInfo(params_.frame_url, ssl,
1403                                         false);  // Don't show the history.
1404      break;
1405    }
1406
1407    case IDC_CONTENT_CONTEXT_UNDO:
1408      source_tab_contents_->render_view_host()->Undo();
1409      break;
1410
1411    case IDC_CONTENT_CONTEXT_REDO:
1412      source_tab_contents_->render_view_host()->Redo();
1413      break;
1414
1415    case IDC_CONTENT_CONTEXT_CUT:
1416      source_tab_contents_->render_view_host()->Cut();
1417      break;
1418
1419    case IDC_CONTENT_CONTEXT_COPY:
1420      source_tab_contents_->render_view_host()->Copy();
1421      break;
1422
1423    case IDC_CONTENT_CONTEXT_PASTE:
1424      source_tab_contents_->render_view_host()->Paste();
1425      break;
1426
1427    case IDC_CONTENT_CONTEXT_DELETE:
1428      source_tab_contents_->render_view_host()->Delete();
1429      break;
1430
1431    case IDC_CONTENT_CONTEXT_SELECTALL:
1432      source_tab_contents_->render_view_host()->SelectAll();
1433      break;
1434
1435    case IDC_CONTENT_CONTEXT_SEARCHWEBFOR:
1436    case IDC_CONTENT_CONTEXT_GOTOURL: {
1437      OpenURL(selection_navigation_url_, NEW_FOREGROUND_TAB,
1438              PageTransition::LINK);
1439      break;
1440    }
1441
1442    case IDC_SPELLCHECK_SUGGESTION_0:
1443    case IDC_SPELLCHECK_SUGGESTION_1:
1444    case IDC_SPELLCHECK_SUGGESTION_2:
1445    case IDC_SPELLCHECK_SUGGESTION_3:
1446    case IDC_SPELLCHECK_SUGGESTION_4:
1447      source_tab_contents_->render_view_host()->Replace(
1448          params_.dictionary_suggestions[id - IDC_SPELLCHECK_SUGGESTION_0]);
1449      break;
1450
1451    case IDC_CHECK_SPELLING_OF_THIS_FIELD:
1452      source_tab_contents_->render_view_host()->ToggleSpellCheck();
1453      break;
1454    case IDC_SPELLCHECK_ADD_TO_DICTIONARY: {
1455      SpellCheckHost* spellcheck_host = profile_->GetSpellCheckHost();
1456      if (!spellcheck_host) {
1457        NOTREACHED();
1458        break;
1459      }
1460      spellcheck_host->AddWord(UTF16ToUTF8(params_.misspelled_word));
1461      SpellCheckerPlatform::AddWord(params_.misspelled_word);
1462      break;
1463    }
1464
1465    case IDC_CONTENT_CONTEXT_LANGUAGE_SETTINGS: {
1466      std::string url = std::string(chrome::kChromeUISettingsURL) +
1467          chrome::kLanguageOptionsSubPage;
1468      OpenURL(GURL(url), NEW_FOREGROUND_TAB, PageTransition::LINK);
1469      break;
1470    }
1471
1472    case IDC_SPELLPANEL_TOGGLE:
1473      source_tab_contents_->render_view_host()->ToggleSpellPanel(
1474          SpellCheckerPlatform::SpellingPanelVisible());
1475      break;
1476
1477#if defined(OS_MACOSX)
1478    case IDC_WRITING_DIRECTION_DEFAULT:
1479      // WebKit's current behavior is for this menu item to always be disabled.
1480      NOTREACHED();
1481      break;
1482    case IDC_WRITING_DIRECTION_RTL:
1483    case IDC_WRITING_DIRECTION_LTR: {
1484      WebKit::WebTextDirection dir = WebKit::WebTextDirectionLeftToRight;
1485      if (id == IDC_WRITING_DIRECTION_RTL)
1486        dir = WebKit::WebTextDirectionRightToLeft;
1487      source_tab_contents_->render_view_host()->UpdateTextDirection(dir);
1488      source_tab_contents_->render_view_host()->NotifyTextDirection();
1489      break;
1490    }
1491    case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY:
1492      LookUpInDictionary();
1493      break;
1494#endif  // OS_MACOSX
1495
1496    default:
1497      NOTREACHED();
1498      break;
1499  }
1500}
1501
1502void RenderViewContextMenu::MenuWillShow() {
1503  RenderWidgetHostView* view = source_tab_contents_->GetRenderWidgetHostView();
1504  if (view)
1505    view->ShowingContextMenu(true);
1506}
1507
1508void RenderViewContextMenu::MenuClosed() {
1509  RenderWidgetHostView* view = source_tab_contents_->GetRenderWidgetHostView();
1510  if (view)
1511    view->ShowingContextMenu(false);
1512  if (source_tab_contents_->render_view_host()) {
1513    source_tab_contents_->render_view_host()->ContextMenuClosed(
1514        params_.custom_context);
1515  }
1516}
1517
1518bool RenderViewContextMenu::IsDevCommandEnabled(int id) const {
1519  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
1520  if (command_line.HasSwitch(switches::kAlwaysEnableDevTools))
1521    return true;
1522
1523  NavigationEntry *active_entry =
1524      source_tab_contents_->controller().GetActiveEntry();
1525  if (!active_entry)
1526    return false;
1527
1528  // Don't inspect view source.
1529  if (active_entry->IsViewSourceMode())
1530    return false;
1531
1532  // Don't inspect about:network, about:memory, etc.
1533  // However, we do want to inspect about:blank, which is often
1534  // used by ordinary web pages.
1535  if (active_entry->virtual_url().SchemeIs(chrome::kAboutScheme) &&
1536      !LowerCaseEqualsASCII(active_entry->virtual_url().path(), "blank"))
1537    return false;
1538
1539  if (id == IDC_CONTENT_CONTEXT_INSPECTELEMENT) {
1540    // Don't enable the web inspector if JavaScript is disabled.
1541    if (!profile_->GetPrefs()->GetBoolean(prefs::kWebKitJavascriptEnabled) ||
1542        command_line.HasSwitch(switches::kDisableJavaScript))
1543      return false;
1544    // Don't enable the web inspector if the developer tools are disabled via
1545    // the preference dev-tools-disabled.
1546    if (profile_->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled))
1547      return false;
1548  }
1549
1550  return true;
1551}
1552
1553string16 RenderViewContextMenu::PrintableSelectionText() {
1554  return l10n_util::TruncateString(params_.selection_text,
1555                                   kMaxSelectionTextLength);
1556}
1557
1558// Controller functions --------------------------------------------------------
1559
1560void RenderViewContextMenu::OpenURL(
1561    const GURL& url,
1562    WindowOpenDisposition disposition,
1563    PageTransition::Type transition) {
1564  source_tab_contents_->OpenURL(url, GURL(), disposition, transition);
1565}
1566
1567void RenderViewContextMenu::CopyImageAt(int x, int y) {
1568  source_tab_contents_->render_view_host()->CopyImageAt(x, y);
1569}
1570
1571void RenderViewContextMenu::Inspect(int x, int y) {
1572  UserMetrics::RecordAction(UserMetricsAction("DevTools_InspectElement"),
1573                            profile_);
1574  DevToolsManager::GetInstance()->InspectElement(
1575      source_tab_contents_->render_view_host(), x, y);
1576}
1577
1578void RenderViewContextMenu::WriteURLToClipboard(const GURL& url) {
1579  chrome_browser_net::WriteURLToClipboard(
1580      url,
1581      profile_->GetPrefs()->GetString(prefs::kAcceptLanguages),
1582      g_browser_process->clipboard());
1583}
1584
1585void RenderViewContextMenu::MediaPlayerActionAt(
1586    const gfx::Point& location,
1587    const WebMediaPlayerAction& action) {
1588  source_tab_contents_->render_view_host()->MediaPlayerActionAt(
1589      location, action);
1590}
1591