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