1// Copyright (c) 2012 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 "chrome/browser/ui/toolbar/wrench_menu_model.h"
6
7#include <algorithm>
8#include <cmath>
9
10#include "base/command_line.h"
11#include "base/prefs/pref_service.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/string_util.h"
14#include "base/strings/utf_string_conversions.h"
15#include "chrome/app/chrome_command_ids.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/defaults.h"
18#include "chrome/browser/extensions/extension_toolbar_model.h"
19#include "chrome/browser/extensions/extension_util.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/profiles/profile_manager.h"
22#include "chrome/browser/search/search.h"
23#include "chrome/browser/signin/signin_manager_factory.h"
24#include "chrome/browser/signin/signin_ui_util.h"
25#include "chrome/browser/task_manager/task_manager.h"
26#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
27#include "chrome/browser/ui/browser.h"
28#include "chrome/browser/ui/browser_commands.h"
29#include "chrome/browser/ui/browser_finder.h"
30#include "chrome/browser/ui/browser_window.h"
31#include "chrome/browser/ui/global_error/global_error.h"
32#include "chrome/browser/ui/global_error/global_error_service.h"
33#include "chrome/browser/ui/global_error/global_error_service_factory.h"
34#include "chrome/browser/ui/tabs/tab_strip_model.h"
35#include "chrome/browser/ui/toolbar/bookmark_sub_menu_model.h"
36#include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
37#include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h"
38#include "chrome/browser/ui/zoom/zoom_controller.h"
39#include "chrome/browser/ui/zoom/zoom_event_manager.h"
40#include "chrome/browser/upgrade_detector.h"
41#include "chrome/common/chrome_paths.h"
42#include "chrome/common/chrome_switches.h"
43#include "chrome/common/pref_names.h"
44#include "chrome/common/profiling.h"
45#include "chrome/grit/chromium_strings.h"
46#include "chrome/grit/generated_resources.h"
47#include "components/signin/core/browser/signin_manager.h"
48#include "components/signin/core/common/profile_management_switches.h"
49#include "content/public/browser/host_zoom_map.h"
50#include "content/public/browser/navigation_entry.h"
51#include "content/public/browser/notification_service.h"
52#include "content/public/browser/notification_source.h"
53#include "content/public/browser/notification_types.h"
54#include "content/public/browser/user_metrics.h"
55#include "content/public/browser/web_contents.h"
56#include "extensions/common/feature_switch.h"
57#include "grit/theme_resources.h"
58#include "ui/base/l10n/l10n_util.h"
59#include "ui/base/layout.h"
60#include "ui/base/models/button_menu_item_model.h"
61#include "ui/base/resource/resource_bundle.h"
62#include "ui/gfx/image/image.h"
63#include "ui/gfx/image/image_skia.h"
64
65#if defined(OS_CHROMEOS)
66#include "chromeos/chromeos_switches.h"
67#endif
68
69#if defined(OS_WIN)
70#include "base/win/metro.h"
71#include "base/win/windows_version.h"
72#include "chrome/browser/enumerate_modules_model_win.h"
73#include "chrome/browser/ui/metro_pin_tab_helper_win.h"
74#include "content/public/browser/gpu_data_manager.h"
75#endif
76
77#if defined(USE_ASH)
78#include "ash/shell.h"
79#endif
80
81using base::UserMetricsAction;
82using content::WebContents;
83
84namespace {
85// Conditionally return the update app menu item title based on upgrade detector
86// state.
87base::string16 GetUpgradeDialogMenuItemName() {
88  if (UpgradeDetector::GetInstance()->is_outdated_install() ||
89      UpgradeDetector::GetInstance()->is_outdated_install_no_au()) {
90    return l10n_util::GetStringUTF16(IDS_UPGRADE_BUBBLE_MENU_ITEM);
91  } else {
92    return l10n_util::GetStringUTF16(IDS_UPDATE_NOW);
93  }
94}
95
96}  // namespace
97
98////////////////////////////////////////////////////////////////////////////////
99// EncodingMenuModel
100
101EncodingMenuModel::EncodingMenuModel(Browser* browser)
102    : ui::SimpleMenuModel(this),
103      browser_(browser) {
104  Build();
105}
106
107EncodingMenuModel::~EncodingMenuModel() {
108}
109
110void EncodingMenuModel::Build() {
111  EncodingMenuController::EncodingMenuItemList encoding_menu_items;
112  EncodingMenuController encoding_menu_controller;
113  encoding_menu_controller.GetEncodingMenuItems(browser_->profile(),
114                                                &encoding_menu_items);
115
116  int group_id = 0;
117  EncodingMenuController::EncodingMenuItemList::iterator it =
118      encoding_menu_items.begin();
119  for (; it != encoding_menu_items.end(); ++it) {
120    int id = it->first;
121    base::string16& label = it->second;
122    if (id == 0) {
123      AddSeparator(ui::NORMAL_SEPARATOR);
124    } else {
125      if (id == IDC_ENCODING_AUTO_DETECT) {
126        AddCheckItem(id, label);
127      } else {
128        // Use the id of the first radio command as the id of the group.
129        if (group_id <= 0)
130          group_id = id;
131        AddRadioItem(id, label, group_id);
132      }
133    }
134  }
135}
136
137bool EncodingMenuModel::IsCommandIdChecked(int command_id) const {
138  WebContents* current_tab =
139      browser_->tab_strip_model()->GetActiveWebContents();
140  if (!current_tab)
141    return false;
142  EncodingMenuController controller;
143  return controller.IsItemChecked(browser_->profile(),
144                                  current_tab->GetEncoding(), command_id);
145}
146
147bool EncodingMenuModel::IsCommandIdEnabled(int command_id) const {
148  bool enabled = chrome::IsCommandEnabled(browser_, command_id);
149  // Special handling for the contents of the Encoding submenu. On Mac OS,
150  // instead of enabling/disabling the top-level menu item, the submenu's
151  // contents get disabled, per Apple's HIG.
152#if defined(OS_MACOSX)
153  enabled &= chrome::IsCommandEnabled(browser_, IDC_ENCODING_MENU);
154#endif
155  return enabled;
156}
157
158bool EncodingMenuModel::GetAcceleratorForCommandId(
159    int command_id,
160    ui::Accelerator* accelerator) {
161  return false;
162}
163
164void EncodingMenuModel::ExecuteCommand(int command_id, int event_flags) {
165  chrome::ExecuteCommand(browser_, command_id);
166}
167
168////////////////////////////////////////////////////////////////////////////////
169// ZoomMenuModel
170
171ZoomMenuModel::ZoomMenuModel(ui::SimpleMenuModel::Delegate* delegate)
172    : SimpleMenuModel(delegate) {
173  Build();
174}
175
176ZoomMenuModel::~ZoomMenuModel() {
177}
178
179void ZoomMenuModel::Build() {
180  AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS);
181  AddItemWithStringId(IDC_ZOOM_NORMAL, IDS_ZOOM_NORMAL);
182  AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS);
183}
184
185////////////////////////////////////////////////////////////////////////////////
186// HelpMenuModel
187
188#if defined(GOOGLE_CHROME_BUILD)
189
190class WrenchMenuModel::HelpMenuModel : public ui::SimpleMenuModel {
191 public:
192  HelpMenuModel(ui::SimpleMenuModel::Delegate* delegate,
193                Browser* browser)
194      : SimpleMenuModel(delegate) {
195    Build(browser);
196  }
197  virtual ~HelpMenuModel() {
198  }
199
200 private:
201  void Build(Browser* browser) {
202#if defined(OS_CHROMEOS) && defined(OFFICIAL_BUILD)
203    int help_string_id = IDS_GET_HELP;
204#else
205    int help_string_id = IDS_HELP_PAGE;
206#endif
207    AddItemWithStringId(IDC_HELP_PAGE_VIA_MENU, help_string_id);
208    if (browser_defaults::kShowHelpMenuItemIcon) {
209      ui::ResourceBundle& rb = ResourceBundle::GetSharedInstance();
210      SetIcon(GetIndexOfCommandId(IDC_HELP_PAGE_VIA_MENU),
211              rb.GetNativeImageNamed(IDR_HELP_MENU));
212    }
213
214    AddItemWithStringId(IDC_FEEDBACK, IDS_FEEDBACK);
215  }
216
217  DISALLOW_COPY_AND_ASSIGN(HelpMenuModel);
218};
219
220#endif  // defined(GOOGLE_CHROME_BUILD)
221
222////////////////////////////////////////////////////////////////////////////////
223// ToolsMenuModel
224
225ToolsMenuModel::ToolsMenuModel(ui::SimpleMenuModel::Delegate* delegate,
226                               Browser* browser)
227    : SimpleMenuModel(delegate) {
228  Build(browser);
229}
230
231ToolsMenuModel::~ToolsMenuModel() {}
232
233void ToolsMenuModel::Build(Browser* browser) {
234  bool show_create_shortcuts = true;
235#if defined(OS_CHROMEOS) || defined(OS_MACOSX)
236  show_create_shortcuts = false;
237#elif defined(USE_ASH)
238  if (browser->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH)
239    show_create_shortcuts = false;
240#endif
241
242  if (extensions::util::IsStreamlinedHostedAppsEnabled()) {
243    AddItemWithStringId(IDC_CREATE_HOSTED_APP, IDS_CREATE_HOSTED_APP);
244    AddSeparator(ui::NORMAL_SEPARATOR);
245  } else if (show_create_shortcuts) {
246    AddItemWithStringId(IDC_CREATE_SHORTCUTS, IDS_CREATE_SHORTCUTS);
247    AddSeparator(ui::NORMAL_SEPARATOR);
248  }
249
250  AddItemWithStringId(IDC_MANAGE_EXTENSIONS, IDS_SHOW_EXTENSIONS);
251
252  if (chrome::CanOpenTaskManager())
253    AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
254
255  AddItemWithStringId(IDC_CLEAR_BROWSING_DATA, IDS_CLEAR_BROWSING_DATA);
256
257  AddSeparator(ui::NORMAL_SEPARATOR);
258
259  encoding_menu_model_.reset(new EncodingMenuModel(browser));
260  AddSubMenuWithStringId(IDC_ENCODING_MENU, IDS_ENCODING_MENU,
261                         encoding_menu_model_.get());
262  AddItemWithStringId(IDC_VIEW_SOURCE, IDS_VIEW_SOURCE);
263  AddItemWithStringId(IDC_DEV_TOOLS, IDS_DEV_TOOLS);
264  AddItemWithStringId(IDC_DEV_TOOLS_CONSOLE, IDS_DEV_TOOLS_CONSOLE);
265  AddItemWithStringId(IDC_DEV_TOOLS_DEVICES, IDS_DEV_TOOLS_DEVICES);
266
267#if defined(ENABLE_PROFILING) && !defined(NO_TCMALLOC)
268  AddSeparator(ui::NORMAL_SEPARATOR);
269  AddCheckItemWithStringId(IDC_PROFILING_ENABLED, IDS_PROFILING_ENABLED);
270#endif
271}
272
273////////////////////////////////////////////////////////////////////////////////
274// WrenchMenuModel
275
276WrenchMenuModel::WrenchMenuModel(ui::AcceleratorProvider* provider,
277                                 Browser* browser)
278    : ui::SimpleMenuModel(this),
279      provider_(provider),
280      browser_(browser),
281      tab_strip_model_(browser_->tab_strip_model()) {
282  Build();
283  UpdateZoomControls();
284
285  content_zoom_subscription_ =
286      content::HostZoomMap::GetDefaultForBrowserContext(browser->profile())
287          ->AddZoomLevelChangedCallback(base::Bind(
288              &WrenchMenuModel::OnZoomLevelChanged, base::Unretained(this)));
289
290  browser_zoom_subscription_ = ZoomEventManager::GetForBrowserContext(
291      browser->profile())->AddZoomLevelChangedCallback(
292          base::Bind(&WrenchMenuModel::OnZoomLevelChanged,
293                     base::Unretained(this)));
294
295  tab_strip_model_->AddObserver(this);
296
297  registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
298                 content::NotificationService::AllSources());
299}
300
301WrenchMenuModel::~WrenchMenuModel() {
302  if (tab_strip_model_)
303    tab_strip_model_->RemoveObserver(this);
304}
305
306bool WrenchMenuModel::DoesCommandIdDismissMenu(int command_id) const {
307  return command_id != IDC_ZOOM_MINUS && command_id != IDC_ZOOM_PLUS;
308}
309
310bool WrenchMenuModel::IsItemForCommandIdDynamic(int command_id) const {
311  return command_id == IDC_ZOOM_PERCENT_DISPLAY ||
312#if defined(OS_MACOSX)
313         command_id == IDC_FULLSCREEN ||
314#elif defined(OS_WIN)
315         command_id == IDC_PIN_TO_START_SCREEN ||
316#endif
317         command_id == IDC_UPGRADE_DIALOG ||
318         (!switches::IsNewAvatarMenu() && command_id == IDC_SHOW_SIGNIN);
319}
320
321base::string16 WrenchMenuModel::GetLabelForCommandId(int command_id) const {
322  switch (command_id) {
323    case IDC_ZOOM_PERCENT_DISPLAY:
324      return zoom_label_;
325#if defined(OS_MACOSX)
326    case IDC_FULLSCREEN: {
327      int string_id = IDS_ENTER_FULLSCREEN_MAC;  // Default to Enter.
328      // Note: On startup, |window()| may be NULL.
329      if (browser_->window() && browser_->window()->IsFullscreen())
330        string_id = IDS_EXIT_FULLSCREEN_MAC;
331      return l10n_util::GetStringUTF16(string_id);
332    }
333#elif defined(OS_WIN)
334    case IDC_PIN_TO_START_SCREEN: {
335      int string_id = IDS_PIN_TO_START_SCREEN;
336      WebContents* web_contents =
337          browser_->tab_strip_model()->GetActiveWebContents();
338      MetroPinTabHelper* tab_helper =
339          web_contents ? MetroPinTabHelper::FromWebContents(web_contents)
340                       : NULL;
341      if (tab_helper && tab_helper->IsPinned())
342        string_id = IDS_UNPIN_FROM_START_SCREEN;
343      return l10n_util::GetStringUTF16(string_id);
344    }
345#endif
346    case IDC_UPGRADE_DIALOG:
347      return GetUpgradeDialogMenuItemName();
348    case IDC_SHOW_SIGNIN:
349      DCHECK(!switches::IsNewAvatarMenu());
350      return signin_ui_util::GetSigninMenuLabel(
351          browser_->profile()->GetOriginalProfile());
352    default:
353      NOTREACHED();
354      return base::string16();
355  }
356}
357
358bool WrenchMenuModel::GetIconForCommandId(int command_id,
359                                          gfx::Image* icon) const {
360  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
361  switch (command_id) {
362    case IDC_UPGRADE_DIALOG: {
363      if (UpgradeDetector::GetInstance()->notify_upgrade()) {
364        *icon = rb.GetNativeImageNamed(
365            UpgradeDetector::GetInstance()->GetIconResourceID());
366        return true;
367      }
368      return false;
369    }
370    case IDC_SHOW_SIGNIN: {
371      DCHECK(!switches::IsNewAvatarMenu());
372      GlobalError* error = signin_ui_util::GetSignedInServiceError(
373          browser_->profile()->GetOriginalProfile());
374      if (error) {
375        int icon_id = error->MenuItemIconResourceID();
376        if (icon_id) {
377          *icon = rb.GetNativeImageNamed(icon_id);
378          return true;
379        }
380      }
381      return false;
382    }
383    default:
384      break;
385  }
386  return false;
387}
388
389void WrenchMenuModel::ExecuteCommand(int command_id, int event_flags) {
390  GlobalError* error = GlobalErrorServiceFactory::GetForProfile(
391      browser_->profile())->GetGlobalErrorByMenuItemCommandID(command_id);
392  if (error) {
393    error->ExecuteMenuItem(browser_);
394    return;
395  }
396
397  if (!switches::IsNewAvatarMenu() && command_id == IDC_SHOW_SIGNIN) {
398    // If a custom error message is being shown, handle it.
399    GlobalError* error = signin_ui_util::GetSignedInServiceError(
400        browser_->profile()->GetOriginalProfile());
401    if (error) {
402      error->ExecuteMenuItem(browser_);
403      return;
404    }
405  }
406
407  if (command_id == IDC_HELP_PAGE_VIA_MENU)
408    content::RecordAction(UserMetricsAction("ShowHelpTabViaWrenchMenu"));
409
410  if (command_id == IDC_FULLSCREEN) {
411    // We issue the UMA command here and not in BrowserCommandController or even
412    // FullscreenController since we want to be able to distinguish this event
413    // and a menu which is under development.
414    content::RecordAction(UserMetricsAction("EnterFullScreenWithWrenchMenu"));
415  }
416
417  chrome::ExecuteCommand(browser_, command_id);
418}
419
420bool WrenchMenuModel::IsCommandIdChecked(int command_id) const {
421  if (command_id == IDC_SHOW_BOOKMARK_BAR) {
422    return browser_->profile()->GetPrefs()->GetBoolean(
423        bookmarks::prefs::kShowBookmarkBar);
424  } else if (command_id == IDC_PROFILING_ENABLED) {
425    return Profiling::BeingProfiled();
426  } else if (command_id == IDC_TOGGLE_REQUEST_TABLET_SITE) {
427    return chrome::IsRequestingTabletSite(browser_);
428  }
429
430  return false;
431}
432
433bool WrenchMenuModel::IsCommandIdEnabled(int command_id) const {
434  GlobalError* error = GlobalErrorServiceFactory::GetForProfile(
435      browser_->profile())->GetGlobalErrorByMenuItemCommandID(command_id);
436  if (error)
437    return true;
438
439  return chrome::IsCommandEnabled(browser_, command_id);
440}
441
442bool WrenchMenuModel::IsCommandIdVisible(int command_id) const {
443  switch (command_id) {
444#if defined(OS_WIN)
445    case IDC_VIEW_INCOMPATIBILITIES: {
446      EnumerateModulesModel* loaded_modules =
447          EnumerateModulesModel::GetInstance();
448      if (loaded_modules->confirmed_bad_modules_detected() <= 0)
449        return false;
450      // We'll leave the wrench adornment on until the user clicks the link.
451      if (loaded_modules->modules_to_notify_about() <= 0)
452        loaded_modules->AcknowledgeConflictNotification();
453      return true;
454    }
455    case IDC_PIN_TO_START_SCREEN:
456      return base::win::IsMetroProcess();
457#else
458    case IDC_VIEW_INCOMPATIBILITIES:
459    case IDC_PIN_TO_START_SCREEN:
460      return false;
461#endif
462    case IDC_UPGRADE_DIALOG:
463      return UpgradeDetector::GetInstance()->notify_upgrade();
464#if !defined(OS_LINUX) || defined(USE_AURA)
465    case IDC_BOOKMARK_PAGE:
466      return !chrome::ShouldRemoveBookmarkThisPageUI(browser_->profile());
467    case IDC_BOOKMARK_ALL_TABS:
468      return !chrome::ShouldRemoveBookmarkOpenPagesUI(browser_->profile());
469#endif
470    default:
471      return true;
472  }
473}
474
475bool WrenchMenuModel::GetAcceleratorForCommandId(
476      int command_id,
477      ui::Accelerator* accelerator) {
478  return provider_->GetAcceleratorForCommandId(command_id, accelerator);
479}
480
481void WrenchMenuModel::ActiveTabChanged(WebContents* old_contents,
482                                       WebContents* new_contents,
483                                       int index,
484                                       int reason) {
485  // The user has switched between tabs and the new tab may have a different
486  // zoom setting.
487  UpdateZoomControls();
488}
489
490void WrenchMenuModel::TabReplacedAt(TabStripModel* tab_strip_model,
491                                    WebContents* old_contents,
492                                    WebContents* new_contents,
493                                    int index) {
494  UpdateZoomControls();
495}
496
497void WrenchMenuModel::TabStripModelDeleted() {
498  // During views shutdown, the tabstrip model/browser is deleted first, while
499  // it is the opposite in gtk land.
500  tab_strip_model_->RemoveObserver(this);
501  tab_strip_model_ = NULL;
502}
503
504void WrenchMenuModel::Observe(int type,
505                              const content::NotificationSource& source,
506                              const content::NotificationDetails& details) {
507  DCHECK(type == content::NOTIFICATION_NAV_ENTRY_COMMITTED);
508  UpdateZoomControls();
509}
510
511// For testing.
512WrenchMenuModel::WrenchMenuModel()
513    : ui::SimpleMenuModel(this),
514      provider_(NULL),
515      browser_(NULL),
516      tab_strip_model_(NULL) {
517}
518
519bool WrenchMenuModel::ShouldShowNewIncognitoWindowMenuItem() {
520  if (browser_->profile()->IsSupervised())
521    return false;
522
523  return !browser_->profile()->IsGuestSession();
524}
525
526void WrenchMenuModel::Build() {
527#if defined(OS_WIN)
528  AddItem(IDC_VIEW_INCOMPATIBILITIES,
529      l10n_util::GetStringUTF16(IDS_VIEW_INCOMPATIBILITIES));
530  EnumerateModulesModel* model =
531      EnumerateModulesModel::GetInstance();
532  if (model->modules_to_notify_about() > 0 ||
533      model->confirmed_bad_modules_detected() > 0)
534    AddSeparator(ui::NORMAL_SEPARATOR);
535#endif
536
537  if (extensions::FeatureSwitch::extension_action_redesign()->IsEnabled())
538    CreateExtensionToolbarOverflowMenu();
539
540  AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB);
541  AddItemWithStringId(IDC_NEW_WINDOW, IDS_NEW_WINDOW);
542
543  if (ShouldShowNewIncognitoWindowMenuItem())
544    AddItemWithStringId(IDC_NEW_INCOGNITO_WINDOW, IDS_NEW_INCOGNITO_WINDOW);
545
546  bookmark_sub_menu_model_.reset(new BookmarkSubMenuModel(this, browser_));
547  AddSubMenuWithStringId(IDC_BOOKMARKS_MENU, IDS_BOOKMARKS_MENU,
548                         bookmark_sub_menu_model_.get());
549
550  if (!browser_->profile()->IsOffTheRecord()) {
551    recent_tabs_sub_menu_model_.reset(new RecentTabsSubMenuModel(provider_,
552                                                                 browser_,
553                                                                 NULL));
554    AddSubMenuWithStringId(IDC_RECENT_TABS_MENU, IDS_RECENT_TABS_MENU,
555                           recent_tabs_sub_menu_model_.get());
556  }
557
558#if defined(OS_WIN)
559  if (base::win::GetVersion() >= base::win::VERSION_WIN8 &&
560      content::GpuDataManager::GetInstance()->CanUseGpuBrowserCompositor()) {
561    if (browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH) {
562      // ASH/Metro mode, add the 'Relaunch Chrome in desktop mode'.
563      AddSeparator(ui::NORMAL_SEPARATOR);
564      AddItemWithStringId(IDC_WIN_DESKTOP_RESTART, IDS_WIN_DESKTOP_RESTART);
565    } else {
566      AddSeparator(ui::NORMAL_SEPARATOR);
567      AddItemWithStringId(IDC_WIN8_METRO_RESTART, IDS_WIN8_METRO_RESTART);
568    }
569  }
570#endif
571
572  // Append the full menu including separators. The final separator only gets
573  // appended when this is a touch menu - otherwise it would get added twice.
574  CreateCutCopyPasteMenu();
575
576  if (CommandLine::ForCurrentProcess()->HasSwitch(
577          switches::kEnableDomDistiller)) {
578    AddItemWithStringId(IDC_DISTILL_PAGE, IDS_DISTILL_PAGE);
579  }
580
581  AddItemWithStringId(IDC_SAVE_PAGE, IDS_SAVE_PAGE);
582  AddItemWithStringId(IDC_FIND, IDS_FIND);
583  AddItemWithStringId(IDC_PRINT, IDS_PRINT);
584
585  tools_menu_model_.reset(new ToolsMenuModel(this, browser_));
586  CreateZoomMenu();
587
588  AddItemWithStringId(IDC_SHOW_HISTORY, IDS_SHOW_HISTORY);
589  AddItemWithStringId(IDC_SHOW_DOWNLOADS, IDS_SHOW_DOWNLOADS);
590  AddSeparator(ui::NORMAL_SEPARATOR);
591
592#if !defined(OS_CHROMEOS)
593  if (!switches::IsNewAvatarMenu()) {
594    // No "Sign in to Chromium..." menu item on ChromeOS.
595    SigninManager* signin = SigninManagerFactory::GetForProfile(
596        browser_->profile()->GetOriginalProfile());
597    if (signin && signin->IsSigninAllowed()) {
598      const base::string16 short_product_name =
599          l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME);
600      AddItem(IDC_SHOW_SYNC_SETUP, l10n_util::GetStringFUTF16(
601          IDS_SYNC_MENU_PRE_SYNCED_LABEL, short_product_name));
602      AddSeparator(ui::NORMAL_SEPARATOR);
603    }
604  }
605#endif
606
607  AddItemWithStringId(IDC_OPTIONS, IDS_SETTINGS);
608
609// On ChromeOS we don't want the about menu option.
610#if !defined(OS_CHROMEOS)
611  AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
612#endif
613
614#if defined(GOOGLE_CHROME_BUILD)
615  help_menu_model_.reset(new HelpMenuModel(this, browser_));
616  AddSubMenuWithStringId(IDC_HELP_MENU, IDS_HELP_MENU,
617                         help_menu_model_.get());
618#endif
619
620#if defined(OS_CHROMEOS)
621  if (CommandLine::ForCurrentProcess()->HasSwitch(
622          chromeos::switches::kEnableRequestTabletSite))
623    AddCheckItemWithStringId(IDC_TOGGLE_REQUEST_TABLET_SITE,
624                             IDS_TOGGLE_REQUEST_TABLET_SITE);
625#endif
626
627  if (browser_defaults::kShowUpgradeMenuItem)
628    AddItem(IDC_UPGRADE_DIALOG, GetUpgradeDialogMenuItemName());
629
630#if defined(OS_WIN)
631  SetIcon(GetIndexOfCommandId(IDC_VIEW_INCOMPATIBILITIES),
632          ui::ResourceBundle::GetSharedInstance().
633              GetNativeImageNamed(IDR_INPUT_ALERT_MENU));
634#endif
635
636  AddGlobalErrorMenuItems();
637
638  AddSeparator(ui::NORMAL_SEPARATOR);
639  AddSubMenuWithStringId(
640      IDC_ZOOM_MENU, IDS_MORE_TOOLS_MENU, tools_menu_model_.get());
641
642  bool show_exit_menu = browser_defaults::kShowExitMenuItem;
643#if defined(OS_WIN)
644  if (browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH)
645    show_exit_menu = false;
646#endif
647
648  if (show_exit_menu) {
649    AddSeparator(ui::NORMAL_SEPARATOR);
650    AddItemWithStringId(IDC_EXIT, IDS_EXIT);
651  }
652
653  RemoveTrailingSeparators();
654}
655
656void WrenchMenuModel::AddGlobalErrorMenuItems() {
657  // TODO(sail): Currently we only build the wrench menu once per browser
658  // window. This means that if a new error is added after the menu is built
659  // it won't show in the existing wrench menu. To fix this we need to some
660  // how update the menu if new errors are added.
661  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
662  // GetSignedInServiceErrors() can modify the global error list, so call it
663  // before iterating through that list below.
664  std::vector<GlobalError*> signin_errors;
665  signin_errors = signin_ui_util::GetSignedInServiceErrors(
666      browser_->profile()->GetOriginalProfile());
667  const GlobalErrorService::GlobalErrorList& errors =
668      GlobalErrorServiceFactory::GetForProfile(browser_->profile())->errors();
669  for (GlobalErrorService::GlobalErrorList::const_iterator
670       it = errors.begin(); it != errors.end(); ++it) {
671    GlobalError* error = *it;
672    DCHECK(error);
673    if (error->HasMenuItem()) {
674#if !defined(OS_CHROMEOS)
675      // Don't add a signin error if it's already being displayed elsewhere.
676      if (std::find(signin_errors.begin(), signin_errors.end(), error) !=
677          signin_errors.end()) {
678        MenuModel* model = this;
679        int index = 0;
680        if (MenuModel::GetModelAndIndexForCommandId(
681                IDC_SHOW_SIGNIN, &model, &index)) {
682          continue;
683        }
684      }
685#endif
686
687      AddItem(error->MenuItemCommandID(), error->MenuItemLabel());
688      int icon_id = error->MenuItemIconResourceID();
689      if (icon_id) {
690        const gfx::Image& image = rb.GetNativeImageNamed(icon_id);
691        SetIcon(GetIndexOfCommandId(error->MenuItemCommandID()),
692                image);
693      }
694    }
695  }
696}
697
698void WrenchMenuModel::CreateExtensionToolbarOverflowMenu() {
699#if defined(TOOLKIT_VIEWS)
700  AddItem(IDC_EXTENSIONS_OVERFLOW_MENU, base::string16());
701  // We only add the separator if there are > 0 items to show in the overflow.
702  extensions::ExtensionToolbarModel* toolbar_model =
703      extensions::ExtensionToolbarModel::Get(browser_->profile());
704  // A count of -1 means all actions are visible.
705  if (toolbar_model->GetVisibleIconCount() != -1)
706    AddSeparator(ui::UPPER_SEPARATOR);
707#endif  // defined(TOOLKIT_VIEWS)
708}
709
710void WrenchMenuModel::CreateCutCopyPasteMenu() {
711  AddSeparator(ui::LOWER_SEPARATOR);
712
713#if defined(OS_POSIX) && !defined(TOOLKIT_VIEWS)
714  // WARNING: Mac does not use the ButtonMenuItemModel, but instead defines the
715  // layout for this menu item in Toolbar.xib. It does, however, use the
716  // command_id value from AddButtonItem() to identify this special item.
717  edit_menu_item_model_.reset(new ui::ButtonMenuItemModel(IDS_EDIT, this));
718  edit_menu_item_model_->AddGroupItemWithStringId(IDC_CUT, IDS_CUT);
719  edit_menu_item_model_->AddGroupItemWithStringId(IDC_COPY, IDS_COPY);
720  edit_menu_item_model_->AddGroupItemWithStringId(IDC_PASTE, IDS_PASTE);
721  AddButtonItem(IDC_EDIT_MENU, edit_menu_item_model_.get());
722#else
723  // WARNING: views/wrench_menu assumes these items are added in this order. If
724  // you change the order you'll need to update wrench_menu as well.
725  AddItemWithStringId(IDC_CUT, IDS_CUT);
726  AddItemWithStringId(IDC_COPY, IDS_COPY);
727  AddItemWithStringId(IDC_PASTE, IDS_PASTE);
728#endif
729
730  AddSeparator(ui::UPPER_SEPARATOR);
731}
732
733void WrenchMenuModel::CreateZoomMenu() {
734  // This menu needs to be enclosed by separators.
735  AddSeparator(ui::LOWER_SEPARATOR);
736
737#if defined(OS_POSIX) && !defined(TOOLKIT_VIEWS)
738  // WARNING: Mac does not use the ButtonMenuItemModel, but instead defines the
739  // layout for this menu item in Toolbar.xib. It does, however, use the
740  // command_id value from AddButtonItem() to identify this special item.
741  zoom_menu_item_model_.reset(
742      new ui::ButtonMenuItemModel(IDS_ZOOM_MENU, this));
743  zoom_menu_item_model_->AddGroupItemWithStringId(
744      IDC_ZOOM_MINUS, IDS_ZOOM_MINUS2);
745  zoom_menu_item_model_->AddButtonLabel(IDC_ZOOM_PERCENT_DISPLAY,
746                                        IDS_ZOOM_PLUS2);
747  zoom_menu_item_model_->AddGroupItemWithStringId(
748      IDC_ZOOM_PLUS, IDS_ZOOM_PLUS2);
749  zoom_menu_item_model_->AddSpace();
750  zoom_menu_item_model_->AddItemWithImage(
751      IDC_FULLSCREEN, IDR_FULLSCREEN_MENU_BUTTON);
752  AddButtonItem(IDC_ZOOM_MENU, zoom_menu_item_model_.get());
753#else
754  // WARNING: views/wrench_menu assumes these items are added in this order. If
755  // you change the order you'll need to update wrench_menu as well.
756  AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS);
757  AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS);
758  AddItemWithStringId(IDC_FULLSCREEN, IDS_FULLSCREEN);
759#endif
760
761  AddSeparator(ui::UPPER_SEPARATOR);
762}
763
764void WrenchMenuModel::UpdateZoomControls() {
765  int zoom_percent = 100;
766  if (browser_->tab_strip_model()->GetActiveWebContents()) {
767    zoom_percent = ZoomController::FromWebContents(
768                       browser_->tab_strip_model()->GetActiveWebContents())
769                       ->GetZoomPercent();
770  }
771  zoom_label_ = l10n_util::GetStringFUTF16(
772      IDS_ZOOM_PERCENT, base::IntToString16(zoom_percent));
773}
774
775void WrenchMenuModel::OnZoomLevelChanged(
776    const content::HostZoomMap::ZoomLevelChange& change) {
777  UpdateZoomControls();
778}
779