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