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